Cơ chế sự kiện trong Spring Boot

Nguyên lý hoạt động

Cơ chế sự kiện trong Spring Boot dựa trên mẫu thiết kế Observer (Quan sát viên), cho phép các thành phần giao tiếp theo mô hình publish-subscribe (xuất bản - đăng ký) nhằm giảm sự phụ thuộc lẫn nhau. Các thành phần cốt lõi bao gồm:

  1. Sự kiện (Event)
    Tất cả sự kiện đều kế thừa từ lớp ApplicationEvent. Ví dụ định nghĩa sự kiện người dùng đăng ký:
    public class UserRegistrationEvent extends ApplicationEvent {
        private final String userId;
    
        public UserRegistrationEvent(Object source, String userId) {
            super(source);
            this.userId = userId;
        }
    
        public String getUserId() {
            return userId;
        }
    }
  2. Nhà xuất bản (Publisher)
    Sử dụng ApplicationEventPublisher để phát sự kiện. Thường được tiêm vào qua container Spring:
    @Service
    public class RegistrationService {
        private final ApplicationEventPublisher publisher;
    
        public RegistrationService(ApplicationEventPublisher publisher) {
            this.publisher = publisher;
        }
    
        public void register(String userId) {
            // Thực hiện logic nghiệp vụ...
            publisher.publishEvent(new UserRegistrationEvent(this, userId));
        }
    }
  3. Bộ lắng nghe (Listener)
    Có hai cách triển khai phổ biến:
    • Triển khai giao diện ApplicationListener:
      @Component
      public class RegistrationEventListener implements ApplicationListener<UserRegistrationEvent> {
          @Override
          public void onApplicationEvent(UserRegistrationEvent event) {
              System.out.println("Xử lý đăng ký: " + event.getUserId());
          }
      }
    • Sử dụng chú thích @EventListener (khuyến nghị):
      @Component
      public class RegistrationHandler {
          @EventListener
          public void handleRegistration(UserRegistrationEvent event) {
              System.out.println("Xử lý sự kiện đăng ký: " + event.getUserId());
          }
      }
  4. Bộ đa truyền (Multicaster)
    Spring mặc định sử dụng SimpleApplicationEventMulticaster để gửi sự kiện đến tất cả listener phù hợp.

Luồng xử lý sự kiện

  1. Xuất bản: Gọi publishEvent() để đẩy sự kiện vào hệ thống.
  2. Phân phối: Multicaster tìm và gọi các listener đăng ký cho loại sự kiện đó.
  3. Xử lý: Mỗi listener thực thi logic riêng (gửi email, ghi log, cập nhật cache...).

Tính năng nâng cao

1. Xử lý bất đồng bộ

Kết hợp @Async để xử lý không chặn luồng chính:

@Configuration
@EnableAsync
public class AsyncConfiguration {}

@Component
public class AsyncRegistrationHandler {
    @Async
    @EventListener
    public void handleAsync(UserRegistrationEvent event) {
        System.out.println("Xử lý bất đồng bộ: " + event.getUserId());
    }
}

2. Lọc điều kiện với SpEL

Chỉ xử lý khi điều kiện được thỏa mãn:

@EventListener(condition = "#event.userId.length() > 6")
public void handleLongId(UserRegistrationEvent event) {
    System.out.println("ID dài: " + event.getUserId());
}

3. Kiểm soát thứ tự thực thi

Dùng @Order để xác định trình tự gọi listener:

@Order(1)
@EventListener
public void firstHandler(UserRegistrationEvent event) {
    System.out.println("Listener đầu tiên");
}

@Order(2)
@EventListener
public void secondHandler(UserRegistrationEvent event) {
    System.out.println("Listener thứ hai");
}

Ví dụ hoàn chỉnh

Yêu cầu: Sau khi người dùng đăng ký, gửi email chào mừng và ghi log.

// Sự kiện
public class UserRegistrationEvent extends ApplicationEvent {
    private final String userId;
    public UserRegistrationEvent(Object source, String userId) {
        super(source);
        this.userId = userId;
    }
    public String getUserId() { return userId; }
}

// Dịch vụ đăng ký
@Service
public class RegistrationService {
    private final ApplicationEventPublisher publisher;
    public RegistrationService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
    public void register(String userId) {
        System.out.println("Đăng ký người dùng: " + userId);
        publisher.publishEvent(new UserRegistrationEvent(this, userId));
    }
}

// Listener
@Component
public class RegistrationWorkflow {
    @EventListener
    public void sendWelcomeEmail(UserRegistrationEvent event) {
        System.out.println("Gửi email cho: " + event.getUserId());
    }

    @Async
    @EventListener
    public void logActivity(UserRegistrationEvent event) {
        System.out.println("Ghi log đăng ký: " + event.getUserId());
    }
}

Tích hợp với giao dịch: @TransactionalEventListener

Đảm bảo sự kiện chỉ được xử lý sau khi giao dịch thành công:

@Service
public class OrderService {
    private final ApplicationEventPublisher publisher;

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

    @Transactional
    public void completePayment(String orderId) {
        // Cập nhật trạng thái đơn hàng...
        publisher.publishEvent(new PaymentCompletedEvent(this, orderId));
    }
}

@Component
public class NotificationService {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void notifyOnPayment(PaymentCompletedEvent event) {
        System.out.println("Thông báo thanh toán thành công: " + event.getOrderId());
    }
}

Sự kiện kiểu tổng quát (Generic Event)

Cho phép một listener xử lý nhiều loại payload khác nhau:

public class DomainEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
    private final T data;
    public DomainEvent(Object source, T data) {
        super(source);
        this.data = data;
    }
    public T getData() { return data; }
    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(data));
    }
}

@Component
public class EventHandler {
    @EventListener
    public void handleUser(DomainEvent<User> event) {
        System.out.println("Xử lý người dùng: " + event.getData().getName());
    }

    @EventListener
    public void handleOrder(DomainEvent<Order> event) {
        System.out.println("Xử lý đơn hàng: " + event.getData().getId());
    }
}

Kế thừa sự kiện

Listener có thể bắt tất cả sự kiện con của một lớp cha:

public abstract class BaseOrderEvent extends ApplicationEvent {
    public BaseOrderEvent(Object source) { super(source); }
}

public class OrderCreatedEvent extends BaseOrderEvent { /* ... */ }
public class OrderShippedEvent extends BaseOrderEvent { /* ... */ }

@Component
public class OrderMonitor {
    @EventListener
    public void handleAnyOrderEvent(BaseOrderEvent event) {
        System.out.println("Sự kiện đơn hàng: " + event.getClass().getSimpleName());
    }
}

Lưu ý quan trọng

  • An toàn đa luồng: Listener bất đồng bộ phải đảm bảo thread-safe.
  • Xử lý ngoại lệ: Ngoại lệ trong listener sẽ ngăn các listener tiếp theo chạy — nên bao bọc bằng try-catch hoặc cấu hình exception handler toàn cục.
  • Hiệu năng: Tránh thao tác chặn trong listener đồng bộ; ưu tiên xử lý bất đồng bộ cho tác vụ tốn thời gian.

Thẻ: spring-boot event-driven Java spring-framework applicationevent

Đăng vào ngày 19 tháng 5 lúc 09:47