前回までに、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)の活用〜 にご期待ください!


コメント