1. 非同期処理の概要
Spring MVCでは同期処理がリクエスト処理中にブロッキング状態になるため、非同期処理を活用することで高負荷や高IO作業を効率化可能です。Spring MVC 3.2以降はController層でCallableやDeferredResultを返すことで非同期処理を実現できます。
WebAsyncTaskはCallableのラッパーオブジェクトとして、タイムアウトや例外処理などのイベントハンドリングをサポートします。DeferredResultは跨スレッド通信に特化しており、非同期処理の結果を直接設定可能という点が特徴です。
@AsyncアノテーションはRunnableインターフェースの代替として、任意のレイヤーでの非同期処理を実現します。
2. Callableの実装
ControllerメソッドがCallableを返す場合、Spring MVCはTaskExecutorを通じて別スレッドで処理を行います。DispatcherServletとインターセプターはメインスレッドを解放し、レスポンスを保持した状態で待機します。
2.1 Callableサンプルコード
@Controller
public class AsyncCallController {
@RequestMapping("/async-call")
@ResponseBody
public Callable<String> handleAsyncCall() {
return () -> {
long threadId = Thread.currentThread().getId();
Date startTime = new Date();
Thread.sleep(6000L);
Date endTime = new Date();
return String.format("threadId: %d, startTime: %s, endTime: %s",
threadId, startTime, endTime);
};
}
}
2.2 コールバック制限
Callableは直接的なコールバック処理をサポートせず、FutureTaskとの併用が必要です。
3. WebAsyncTaskの利用
WebAsyncTaskはCallableをラッパーオブジェクトとして、タイムアウトや例外処理のハンドリングを簡潔に実装可能です。
3.1 WebAsyncTaskサンプル
@RequestMapping("/async-task")
@ResponseBody
public WebAsyncTask<String> handleAsyncTask() {
WebAsyncTask<String> task = new WebAsyncTask<>(1000, () -> {
Thread.sleep(5000);
return "username:wangbinghua";
});
task.onCompletion(() -> System.out.println("タスク完了"));
task.onTimeout(() -> {
System.out.println("タイムアウト発生");
return "<h1>Time Out</h1>";
});
return task;
}
4. DeferredResultの仕組み
DeferredResultは直接的なタイムアウト設定が可能で、非同期処理の結果を明示的に設定できます。このメカニズムにより、短時間での高いトランザクション処理能力を実現します。
4.1 DeferredResultの使用例
@GetMapping("/deferred-result")
public DeferredResult<String> handleDeferredResult() {
DeferredResult<String> result = new DeferredResult<>(20000L, "初期値");
result.onTimeout(() -> {
log.info("タイムアウト処理");
result.setResult("タイムアウト");
});
result.onCompletion(() -> {
log.info("完了処理");
});
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(10);
result.setResult("OK");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return result;
}
4.2 タイムアウト処理の流れ
- ブラウザから非同期リクエストが送信される
- サーバー側でリクエストが一時保留となる
- DeferredResult.setResult()が呼ばれるか、タイムアウトが発生するまで待機
- 結果が返却され、再び処理が実行される