SpringMVCにおける非同期処理の実装方法

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 タイムアウト処理の流れ

  1. ブラウザから非同期リクエストが送信される
  2. サーバー側でリクエストが一時保留となる
  3. DeferredResult.setResult()が呼ばれるか、タイムアウトが発生するまで待機
  4. 結果が返却され、再び処理が実行される

タグ: SpringMVC Callable WebAsyncTask DeferredResult 非同期処理

9月19日 03:17 投稿