🚀 Spring Framework 講座【第31回】待ち時間をなくす!〜非同期処理(@Async)の活用〜

docs

前回までに、Webアプリケーション開発の四大要素(Spring Core, MVC, JPA, Security)の知識が固まりました。

今回からは、アプリケーションの実用性性能を向上させるためのテクニックに進みます。Webアプリケーションの性能低下の大きな原因の一つは、処理の待ち時間です。

今回は、この待ち時間を解消し、アプリケーションを高速化する**非同期処理(Asynchronous Processing)**の基本と、Spring Bootで非同期処理を簡単に実現する @Async アノテーションの使い方を学びます。


1. 同期処理と非同期処理の違い

Spring MVCのControllerがリクエストを受け付けた際、通常、その処理は**同期処理(Synchronous Processing)**として実行されます。

1-1. 同期処理(Synchronous)

  • 特徴: 処理Aが完了するまで、次の処理Bは待機します。
  • 問題点: DBへの書き込みや、外部APIとの通信など、時間のかかる処理が含まれていると、その間、リクエストを受け付けたスレッド(処理の実行単位)がブロックされ、クライアント(ユーザー)はレスポンスを待たされ続けます。

1-2. 非同期処理(Asynchronous)

  • 特徴: 処理Aを開始した後、完了を待たずにすぐに次の処理Bへ進みます。時間のかかる処理Aは、別のスレッドに任せてバックグラウンドで実行されます。
  • メリット: クライアントへのレスポンスをすぐに返すことができ、Webアプリケーションのスレッドがブロックされずに済み、同時に多くのリクエストを処理できるようになります。

2. Spring Bootで非同期処理を有効にする:@EnableAsync

Spring Bootアプリケーションで @Async アノテーションを使用するためには、まず非同期処理を有効にする必要があります。

メインクラス、または任意の @Configuration クラスに @EnableAsync アノテーションを付与するだけで完了します。

Java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync; // ① これを追加

@SpringBootApplication
@EnableAsync // ② 非同期機能を有効化
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3. メソッドを非同期で実行する:@Async

非同期で実行したいService層のメソッドに @Async アノテーションを付与します。

例えば、「ユーザー登録後にメールを送信する」という処理は、ユーザーが待つ必要のないバックグラウンド処理として最適です。

Java

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class EmailService {
    
    // このメソッドは呼び出し元とは別のスレッドで実行される
    @Async 
    public void sendWelcomeEmail(String emailAddress) {
        System.out.println("【非同期処理】メール送信処理開始。スレッド名: " + Thread.currentThread().getName());
        
        // 外部API呼び出しや待機処理をシミュレーション
        try {
            Thread.sleep(5000); // 5秒かかる処理
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        System.out.println("【非同期処理】" + emailAddress + " へのメール送信が完了しました。");
    }
    
    // ...
}

3-1. Controllerでの呼び出しと効果

Controllerは、この @Async メソッドを通常のメソッドと同様に呼び出すだけです。

Java

@RestController
@RequiredArgsConstructor
public class UserController {
    
    private final EmailService emailService;
    
    @PostMapping("/users")
    public String registerUser(UserRegistrationForm form) {
        // ... (ユーザー登録処理)
        
        // メール送信を呼び出す。
        // 呼び出し側スレッドは、EmailServiceの処理完了を待たずにすぐに次の行へ進む。
        emailService.sendWelcomeEmail(form.getEmail()); 
        
        // クライアントに即座にレスポンスを返す
        return "ユーザー登録が完了しました。メールはバックグラウンドで送信中です。"; 
    }
}

この結果、ユーザーはメール送信に5秒かかっても、すぐにレスポンスを受け取ることができ、アプリケーション全体の応答性が向上します。

4. 実行結果を取得する:Future/CompletableFuture

非同期処理の実行結果(戻り値)を、後から呼び出し元で利用したい場合、メソッドの戻り値の型を Future または CompletableFuture にする必要があります。

Java

import java.util.concurrent.CompletableFuture;

@Service
public class CalculationService {

    // 戻り値の型を CompletableFuture<結果の型> にする
    @Async
    public CompletableFuture<Integer> calculateComplexValue(int input) {
        
        int result = input * 100; // 時間のかかる計算
        
        // 結果を CompletableFuture.completedFuture でラップして返す
        return CompletableFuture.completedFuture(result); 
    }
}

呼び出し元では、この CompletableFuture オブジェクトを受け取り、.get() メソッドで結果を取得できます。ただし、.get() を呼び出すと結果が得られるまで待機してしまうため、非同期のメリットを最大限に活かすには、他の非同期操作と連携させるための thenApply などのメソッドを使うことが推奨されます。

✅ 本日のまとめ

  • 同期処理は処理の完了を待つため、時間のかかる処理があると応答性が低下する。
  • 非同期処理は、時間のかかる処理を別のスレッドで実行することで、応答性を向上させる。
  • @EnableAsync をConfigurationクラスに付与することで、非同期機能を有効化する。
  • @Async をServiceメソッドに付与することで、そのメソッドを非同期で実行できる。
  • 非同期処理の実行結果を利用したい場合は、戻り値の型を Future または CompletableFuture にする。

🔔 次回予告

非同期処理を導入することで性能は上がりましたが、アプリケーションの実行環境はサーバーだけではありません。定期的なバッチ処理や、特定の時間に実行したい処理(例: 日次集計、データのバックアップ)もあります。

次回は、Spring Bootで定期的な処理を簡単に実現する スケジューリングの機能、@Scheduled アノテーションの使い方を学びます。

次回:【第32回】アプリケーションの自動化!〜スケジューリング(@Scheduled)の活用〜 にご期待ください!

コメント

タイトルとURLをコピーしました