Chuyển đến nội dung chính

レッスン 14: 非同期処理、スケジューリング、およびアプリケーション イベント

@Async と仮想スレッド、CompletableFuture。 @バックグラウンドタスク用にスケジュールされています。 Spring アプリケーション イベント — パブリッシュ/サブスクライブ、ドメイン イベント、@TransactionalEventListener。

💻 プログラミング — レッスン 13 レッスン 14: 非同期処理、スケジュール設定、および アプリケーションイベント

Spring Boot 4: 基本から上級まで

パート 4: 高度な機能

xdev.asia

はじめに

非同期処理、タスク スケジューリング、およびイベント駆動型アーキテクチャは、エンタープライズ アプリケーションで広く使用されている 3 つのパターンです。 Spring Boot 4 は仮想スレッド (Java 21+) をサポートしており、非同期処理がこれまでより効率的になります。


1. @Async — 非同期処理

1.1 非同期を有効にする

@Configuration
@EnableAsync
public class AsyncConfig { }

1.2 @Async の使用

@Service
public class NotificationService {

    // Fire-and-forget
    @Async
    public void sendWelcomeEmail(String email, String name) {
        log.info("Sending email to {} on thread: {}",
            email, Thread.currentThread().getName());
        emailClient.send(email, "Welcome " + name, buildWelcomeBody(name));
    }

    // Trả về CompletableFuture
    @Async
    public CompletableFuture<ReportResult> generateReport(ReportRequest request) {
        ReportResult result = reportGenerator.generate(request);
        return CompletableFuture.completedFuture(result);
    }
}

1.3 仮想スレッド (Spring Boot 4 + Java 21+)

# application.yml
spring:
  threads:
    virtual:
      enabled: true  # Tự động dùng Virtual Threads cho @Async
// Custom executor với Virtual Threads
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
    return new TaskExecutorAdapter(
        Executors.newVirtualThreadPerTaskExecutor());
}

1.4 複数の非同期操作を作成する

@Service
public class DashboardService {

    public DashboardResponse getDashboard(Long userId) {
        CompletableFuture<UserStats> statsFuture =
            statsService.getUserStats(userId);
        CompletableFuture<List<Notification>> notifFuture =
            notificationService.getRecent(userId);
        CompletableFuture<List<Order>> ordersFuture =
            orderService.getRecentOrders(userId);

        // Chờ tất cả hoàn thành
        CompletableFuture.allOf(statsFuture, notifFuture, ordersFuture).join();

        return new DashboardResponse(
            statsFuture.join(),
            notifFuture.join(),
            ordersFuture.join()
        );
    }
}

1.5 非同期エラー処理

@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method,
                                         Object... params) {
        log.error("Async error in {}: {}", method.getName(), ex.getMessage(), ex);
    }
}

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }
}

2. @Scheduled — タスクのスケジュール設定

2.1 スケジュールを有効にする

@Configuration
@EnableScheduling
public class SchedulingConfig { }

2.2 スケジュールの種類

@Component
public class ScheduledTasks {

    // Fixed rate: 5 giây một lần (tính từ lúc BẮT ĐẦU task trước)
    @Scheduled(fixedRate = 5000)
    public void checkHealth() {
        log.info("Health check at {}", Instant.now());
    }

    // Fixed delay: 5 giây sau khi task trước HOÀN THÀNH
    @Scheduled(fixedDelay = 5000, initialDelay = 10000)
    public void syncData() {
        log.info("Syncing data...");
    }

    // Cron expression: 2 giờ sáng mỗi ngày
    @Scheduled(cron = "0 0 2 * * *")
    public void dailyCleanup() {
        log.info("Running daily cleanup...");
        tempFileService.deleteExpired();
    }

    // Cấu hình từ properties
    @Scheduled(cron = "${app.scheduler.report-cron:0 0 6 * * MON}")
    public void weeklyReport() {
        reportService.generateWeeklyReport();
    }
}

2.3 Cron 式のチートシート

┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-7, 0=Sun)
│ │ │ │ │ │
* * * * * *

0 0 2 * * *     → 2:00 AM mỗi ngày
0 */15 * * * *   → Mỗi 15 phút
0 0 9-17 * * MON-FRI → Mỗi giờ 9h-17h, thứ 2-6

3. 春のアプリケーションイベント

3.1 カスタム イベントの作成

// Event dùng record
public record OrderCreatedEvent(
    Long orderId,
    Long userId,
    BigDecimal totalAmount,
    Instant createdAt
) { }

3.2 イベントの公開

@Service
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public OrderResponse createOrder(CreateOrderRequest request) {
        Order order = orderRepository.save(mapToOrder(request));

        // Publish event
        eventPublisher.publishEvent(new OrderCreatedEvent(
            order.getId(),
            order.getUserId(),
            order.getTotalAmount(),
            Instant.now()
        ));

        return OrderResponse.from(order);
    }
}

3.3 イベントの購読

@Component
public class OrderEventListener {

    // Đồng bộ (cùng thread, cùng transaction)
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        log.info("Order {} created, amount: {}",
            event.orderId(), event.totalAmount());
    }

    // Async listener
    @Async
    @EventListener
    public void sendOrderConfirmation(OrderCreatedEvent event) {
        emailService.sendOrderConfirmation(event.userId(), event.orderId());
    }

    // Chỉ xử lý SAU khi transaction commit thành công
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void afterOrderCommitted(OrderCreatedEvent event) {
        inventoryService.decreaseStock(event.orderId());
        analyticsService.trackOrder(event);
    }

    // Conditional listener
    @EventListener(condition = "#event.totalAmount() > 1000000")
    public void onHighValueOrder(OrderCreatedEvent event) {
        alertService.notifyManager(event);
    }
}

3.4 アプリケーションのイベント フロー

OrderController
    │
    ▼
OrderService.createOrder()
    │── Save to DB
    │── Publish OrderCreatedEvent
    │
    ▼
┌─────────────────────────────────┐
│  Event Listeners                │
│  ├── NotificationListener       │  ← @Async: gửi email
│  ├── InventoryListener          │  ← @TransactionalEventListener
│  ├── AnalyticsListener          │  ← @Async: track metrics
│  └── AuditListener              │  ← Ghi audit log
└─────────────────────────────────┘

概要

  • @Async と仮想スレッド (Spring Boot 4) を組み合わせて効率的な同時処理を実現し、手動のスレッド プール管理が不要
  • @Scheduled は、バックグラウンド タスクの固定レート、固定遅延、cron 式をサポートします
  • アプリケーション イベント (パブリッシュ/サブスクライブ) は、ビジネス ロジックの分離に役立ちます。@TransactionalEventListener は、トランザクションのコミット後にのみイベントが処理されるようにします。

演習

  1. 非同期電子メール サービスを実装する: ユーザーを作成した後、非同期のウェルカム電子メールを送信します。 AsyncUncaughtExceptionHandler でエラーを処理する
  2. 毎日午前 2 時に実行するスケジュールされたタスクを作成し、期限切れのセッションと一時ファイルを削除します
  3. イベント駆動型の注文処理を実装します: @TransactionalEventListener を使用して、OrderCreatedEvent → NoticeListener + InventoryListener + AnalyticsListener