@Async と @RequestScope を同時につかうとどうなるか @RequestScope、これは @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) と同じ意味。この Bean はスレッドローカルにひもづけられているHttpServletRequestに保持されている。
@Async は別スレッドで処理を実行するため、間接的にでも @RequestScope な Bean を使っていると以下のような実行時エラーとなる。処理中のスレッドには該当する Bean が保持されていないという旨。
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.
ScopedProxyMode.TARGET_CLASS なのでスタックトレースがややこしい。cglib を使ってクラスのプロキシを作って、プロキシクラス内で Bean をどこかからとってきて呼ぶという動きになっている。おかげでスコープに関わらずコード上は Singleton のように扱えているが、黒魔術はひっかかったとき大変です。
解決方法 https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor に良い解決方法が書いてあって、これを使えばよい。やってることは単純で @Async で別スレッドで実行しようとする際に、呼び出し元スレッドのBeanを渡してあげるという感じ。
ただし、どうもコピペでは動かなくて、以下のようにかえてうまくいった。結局 Executor インターフェイスのメソッドだけオーバーライドしたら良い。
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
@Override
@Bean
public Executor getAsyncExecutor () {
val executor = new ContextAwarePoolExecutor();
executor.setCorePoolSize(25 );
executor.setQueueCapacity(25 );
executor.setMaxPoolSize(25 );
executor.setThreadNamePrefix("AsyncTask-" );
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler () {
return null ;
}
@SuppressWarnings ("serial" )
public static class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute (Runnable task) {
super .execute(new ContextAwareRunnable(task, RequestContextHolder.currentRequestAttributes()));
}
}
public static class ContextAwareRunnable implements Runnable {
private final Runnable task;
private final RequestAttributes context;
public ContextAwareRunnable (Runnable task, RequestAttributes context) {
this .task = task;
this .context = context;
}
@Override
public void run () {
if (context != null ) {
RequestContextHolder.setRequestAttributes(context);
}
try {
task.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
}
トップ
tech
Spring MVC で @Async と @RequestScope を共存させるには?