Spring MVC で @Async と @RequestScope を共存させるには?
@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 {
// Default task executor for @Async annotation
@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 Executor interface
*/
@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();
}
}
}
} 関連エントリー
- mbed USBSerial を WebUSB から扱うには mbed USBDevice ライブラリの中に USB CDC で動く USBSerial クラスが実装されている。これを Web USB ...
- MCD-ST Liberty SW License Agreement V2 はフリーなライセンスか? STM32CubeMX でジェネレートされるコードは MCD-ST Liberty SW License Agreement V2 というラ...
- WSL2 で USB (usbipd-win) https://github.com/dorssel/usbipd-win WSL2 内ではホストの USB デバイスを使うことができないと...
- Node.js の fs モジュールだけで GPIO の割込みを扱うには? 以下のようにすれば動くことが確認できた。 select や poll を明示的に呼び出すことができないが、edge を設定しさえすれば fs...
- ブログシステムの HTML 生成を効率化 そろそろやることなくなったので minify などをやることにしました。 ただ、ブログシステムの出力の最後ほうでページごとに全体を mini...