Solución:
Nos encontramos con el mismo problema: necesitábamos ejecutar código en segundo plano usando @Async, por lo que no pudo usar ningún bean Session o RequestScope. Lo solucionamos de la siguiente manera:
- Cree un TaskPoolExecutor personalizado que almacene información de ámbito con las tareas
- Cree un Invocable (o Ejecutable) especial que use la información para establecer y borrar el contexto para el hilo de fondo
- Cree una configuración de anulación para usar el ejecutor personalizado
Nota: esto solo funcionará para beans con ámbito de sesión y solicitud, y no para el contexto de seguridad (como en Spring Security). Tendría que usar otro método para establecer el contexto de seguridad si eso es lo que busca.
Nota 2: Para mayor brevedad, solo se muestra la implementación Callable y submit (). Puede hacer lo mismo para Runnable y execute ().
Aquí está el código:
Ejecutor:
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()));
}
}
Invocable:
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();
}
}
}
Configuración:
@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
@Override
@Bean
public Executor getAsyncExecutor() {
return new ContextAwarePoolExecutor();
}
}
La forma más sencilla es utilizar un decorador de tareas como este:
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();
}
};
}
}
Para agregar este decorador al ejecutor de tareas, todo lo que necesita es agregarlo en la rutina de configuración:
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
poolExecutor.initialize();
return poolExecutor;
}
No es necesario un titular adicional o un ejecutor de tareas de grupo de subprocesos personalizado.
Una pequeña actualización para 2021: usando las versiones actuales de Spring Boot, la mera existencia de un bean de tipo TaskDecorator
Será suficiente. Al crear el contexto, el decorador de tareas se utilizará para decorar los ejecutores que crea Spring Boot.
No hay forma de obtener un objeto de alcance de solicitud en un subproceso asíncrono secundario, ya que el subproceso de procesamiento de solicitud principal original ya puede haber enviado la respuesta al cliente y todos los objetos de solicitud se destruyen. Una forma de manejar estos escenarios es usar un alcance personalizado, como SimpleThreadScope.
Un problema con SimpleThreadScope es que los subprocesos secundarios no heredarán las variables de alcance de los padres, porque usa ThreadLocal simple internamente. Para superar eso, implemente un alcance personalizado que sea exactamente similar a SimpleThreadScope pero use InheritableThreadLocal internamente. Para obtener más información sobre este Spring MVC: ¿Cómo usar un bean con alcance de solicitud dentro de un hilo generado?