Comment activer la requête scope dans async task executor

Dans mon application j'ai quelques services web asynchrones. Le serveur accepte la requête, renvoie la réponse OK et commence le traitement de la requête avec AsyncTaskExecutor. Ma question Est de savoir comment activer la requête scope ici parce que dans ce traitement j'ai besoin d'obtenir la classe qui est annotée par:

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)

Maintenant-je obtenir de l'exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.requestContextImpl': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

parce qu'il fonctionne dans SimpleAsyncTaskExecutor et pas DispatcherServlet

mon async traitement de la requête

taskExecutor.execute(new Runnable() {

    @Override
    public void run() {
        asyncRequest(request);
    }
});

où taskExecutor est:

<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
19
demandé sur hudi 2014-05-19 11:32:11

3 réponses

nous avons rencontré le même problème - nécessaire pour exécuter le code en arrière - plan en utilisant @Async, donc il était incapable d'utiliser n'importe quelle fève Session-ou RequestScope. Nous l'avons résolu de la façon suivante:

  • Créer un personnalisé TaskPoolExecutor qui stocke l'étendue de l'information avec les tâches
  • créer un Callable spécial (ou Runnable) qui utilise l'information pour définir et effacer le contexte du thread d'arrière-plan
  • créer un contrôleur de configuration pour utiliser le exécuteur

Remarque:: cela ne fonctionnera que pour la Session et la demande scoped beans, et pas pour le contexte de sécurité (comme dans la sécurité de printemps). Vous devez utiliser une autre méthode pour définir le contexte de sécurité si c'est ce que vous recherchez.

Note2: par souci de brièveté, n'a montré que l'implémentation Callable et submit (). Vous pouvez faire la même chose pour Runnable et execute().

Voici la code:

Exécuteur testamentaire:

public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
    }
}

Appelable:

public class ContextAwareCallable<T> implements Callable<T> {
    private Callable<T> task;
    private RequestAttributes context;

    public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
        this.task = task;
        this.context = context;
    }

    @Override
    public T call() throws Exception {
        if (context != null) {
            RequestContextHolder.setRequestAttributes(context);
        }

        try {
            return task.call();
        } finally {
            RequestContextHolder.resetRequestAttributes();
        }
    }
}

Configuration:

@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
    @Override
    @Bean
    public Executor getAsyncExecutor() {
        return new ContextAwarePoolExecutor();
    }
}
49
répondu Armadillo 2015-10-26 02:59:51

il n'y a aucun moyen d'obtenir un objet request scoped dans un thread async enfant, puisque le thread original de traitement de requête parent peut avoir déjà engagé la réponse au client et tous les objets request sont détruits. Une façon de gérer de tels scénarios est de l'utilisation étendue personnalisée, comme SimpleThreadScope.

un problème avec SimpleThreadScope est que les threads enfant n'hériteront pas des variables de parents scope, parce qu'il utilise un ThreadLocal simple en interne. De surmonter que mettre en œuvre un scope personnalisé qui est exactement similaire à SimpleThreadScope, mais utilise Héritablethreadlocal en interne. Pour plus d'info reg ce MVC de printemps: comment utiliser un bean request-scoped à l'intérieur d'un thread spawned?

8
répondu Thilak 2017-05-23 12:18:01

Le plus simple est d'utiliser une tâche décorateur, comme ceci:

static class ContextCopyingDecorator implements TaskDecorator {
    @Nonnull
    @Override
    public Runnable decorate(@Nonnull Runnable runnable) {
        RequestAttributes context =
                RequestContextHolder.currentRequestAttributes();
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                MDC.setContextMap(contextMap);
                runnable.run();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

Pour ajouter cette décorateur à l'exécuteur de tâches, il vous suffit de l'ajouter dans la configuration de routine:

@Override
@Bean
public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
    poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
    poolExecutor.initialize();
    return poolExecutor;
}

il n'y a pas besoin d'un support supplémentaire ou d'un exécuteur de tâches personnalisé.

5
répondu Michael Piefel 2018-05-02 16:00:21