참고) https://velog.io/@penrose_15/Spring-Boot-EventHandler
https://gudwnsgur.tistory.com/19
https://wildeveloperetrain.tistory.com/246
ApplicationContext로 넘겨주고 이를 Listener에서 받아서 처리한다
Event Class와 이벤트를 발생시키는 Event Publisher 그리고 이벤트를 받아들이는 Event Listener 3가지 요소로 볼 수 있다Observer패턴이 구현되어 있다동기 방식으로 동작한다public class OrderedEvent {
private String productName;
public OrderedEvent(String productName) {
this.productName = productName;
}
public String getProductName() {
return productName;
}
}
publishEvent() 메서드를 통해 생성된 이벤트 객체를 넣어준다ApplicationEventPublisher를 상속하고 있기 때문에 Publisher와 Listener는 스프링 빈으로 등록되어야 한다@EventListener, @TransactionalEventListener를 ApplicationContext에 등록을 해두고 publishEvent()가 실행되면 등록된 Listener에 Event를 뿌려준다public class OrderService {
ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public void order(String productName) {
publisher.publishEvent(new OrderedEvent(productName));
}
}
ApplicationListener<CustomEvent>를 상속받아서 썻으나 현재는 애노테이션으로 이벤트를 캐치할 수 있다@EventListener가 CustomEvent 클래스를 파라미터로 받는 메서드를 모두 실행하기 때문에 이벤트를 받을 파라미터의 중복에 주의 해야 한다 @EventListener
public void sendPush(OrderedEvent event) throws InterruptedException {
log.info(String.format("푸시 메세지 발송 [상품명 : %s]", event.getProductName()));
}
EventListener는 같은 트랜잭션에 묶여 있어서 만약 주문은 성공적으로 끝났는데 알림서비스에서 문제가 발생긴다면 전부 롤백시켜야 하는 경우가 발생한다
TransactionalEventListener을 사용하면 된다After_Commit으로 트랜잭션이 성공적으로 커밋된 후 Listener로직을 수행하라는 뜻이다옵션
- Before_Commit : 커밋되기 전에 실행한다. 이럴 경우 TransactionalEventListener을 쓰는 이유가 없어진다
- After_Commit : 기본 값으로, 커밋이 성공적으로 완료된 후에만 실행되므로, 안정적인 후속 처리가 필요할 때 유용하다
- After_Rollback: 트랜잭션이 실패하고 롤백된 후에 이벤트를 처리하거나 롤백 작업을 할 때 유용하다
- After_Completed: After_Rollback + After_Commit
- 트랜잭션이 완료된 후(커밋 또는 롤백 후)에 실행된다
@RequiredArgsConstructor
@Component
public class CustomEventListener {
private final NotificationService notificationService;
// 이벤트 발행된 곳의 트랜잭션 완료 후 실행
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handler(Notice notice) {
notificationService.createNotification(notice);
}
}
주의점
- After_Commit을 사용시 이전의 이벤트를 publish 하는 코드에서 트랜잭션이 이미 커밋되었기 때문에
After_Commit이후 새로운 트랜잭션 수행 시 트랜잭션을 커밋하지 않는다@TransactionalEventListener의 경우 event publisher의 트랜잭션 안에서 동작하며, 커밋이 된 이후 추가 커밋을 허용하지 않는다- 즉 After_Commit을 사용하면 트랜잭션은 적용되지 않는다
- 그래서
Propagation = Require_new를 통해 새로운 트랜잭션을 열어 수행해줘야 한다
// 트랜잭션 커밋 후 이벤트 발행
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
eventPublisher.publishEvent(new ItemRegistrationEvent(this, item.getName(), item.getPrice()));
}
});
@Async와 EnableAsync만 선언해주면 끝난다 @Async // 추가
@EventListener
public void sendSms(RegisteredEvent event) throws InterruptedException {
Thread.sleep(2000);
System.out.println(event.getName() + "님에게 가입 축하 메세지를 전송했습니다.");
}
@EnableAsync
@Configuration
public class AsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(3); // 기본 스레드 수
threadPoolTaskExecutor.setMaxPoolSize(30); // 최대 스레드 수
threadPoolTaskExecutor.setQueueCapacity(10); // Queue 수
threadPoolTaskExecutor.setThreadNamePrefix("Executor-");
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}