1. 스프링 이벤트란?
스프링 이벤트는 Observer 패턴을 기반으로 동작합니다. 특정 이벤트가 발생하면, 그 이벤트를 "구독"하고 있는 리스너들이 호출되는 방식이죠. 이를 통해 비동기 처리, 로깅, 알림 시스템 등 다양한 곳에 활용할 수 있습니다.
✨ 왜 사용할까요?
- 느슨한 결합: 이벤트 발행자와 리스너가 서로의 존재를 몰라도 됩니다.
- 유지보수 용이: 기능 추가나 변경 시 다른 코드에 영향을 최소화합니다.
- 비동기 처리 가능: 이벤트 리스너를 비동기로 처리하여 성능을 개선할 수 있습니다.
- 트랜잭션 후처리: 트랜잭션 완료 후 안전하게 이벤트를 처리할 수 있습니다.
2. 스프링 이벤트 시스템: 개념과 실습
스프링 이벤트 시스템은 크게 이벤트(Event), 이벤트 발행자(Publisher), 그리고 이벤트 리스너(Listener)로 나뉩니다. 이 섹션에서는 각 구성 요소를 설명하고, 이를 활용한 간단한 이벤트 시스템을 직접 구현해보겠습니다.
2.1 이벤트 클래스 정의
이벤트 클래스는 단순한 POJO로 만들어도 됩니다.
// CustomEvent.java
public class CustomEvent {
private String message;
public CustomEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
2.2 이벤트 발행자
이벤트 발행자는 ApplicationEventPublisher 인터페이스를 통해 이벤트를 발행합니다.
// EventPublisherService.java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class EventPublisherService {
private final ApplicationEventPublisher publisher;
public EventPublisherService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void publish(String message) {
CustomEvent event = new CustomEvent(message);
publisher.publishEvent(event);
}
}
2.3 이벤트 리스너
리스너는 @EventListener 어노테이션을 사용하거나 ApplicationListener 인터페이스를 구현하여 이벤트를 처리합니다.
// CustomEventListener.java
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("이벤트 수신: " + event.getMessage());
}
}
2.4 컨트롤러
컨트롤러를 작성하여 EventPublisherService를 호출하겠습니다.
// EventController.java
@RestController
@RequiredArgsConstructor
public class EventController {
private final EventPublisherService publisherService;
@GetMapping("/publish")
public String publishEvent(@RequestParam String message) {
publisherService.publish(message);
return "이벤트 발행 완료: " + message;
}
}
2.4 동작확인
포스트맨으로 서버로 요청을 보내 api를 테스트 합니다.

스프링 콘솔을 보면 이벤트 리스너에서 출력한 로그를 확인할 수 있습니다.

3. 트랜잭션 처리 및 비동기 이벤트
스프링 이벤트 시스템은 트랜잭션과 비동기 처리와의 관계에 따라 이벤트 처리 방식이 달라집니다. 이 섹션에서는 트랜잭션 상태에 따른 이벤트 처리 및 비동기 이벤트 처리 방법을 소개합니다.
3.1 @TransactionalEventListener의 phase 속성
@TransactionalEventListener는 트랜잭션의 상태에 따라 이벤트 리스너의 실행 시점을 제어할 수 있습니다.
phase 값설명
phase | 값설명 | 예외 전달 | 트랜잭션 롤백 여부 |
BEFORE_COMMIT | 트랜잭션 커밋 직전에 이벤트 리스너 실행 | 예 | 예 (트랜잭션 롤백됨) |
AFTER_COMMIT (기본값) | 트랜잭션 커밋 후 이벤트 리스너 실행 | 아니요 | 아니요 (트랜잭션에 영향 없음) |
AFTER_ROLLBACK | 트랜잭션 롤백 후 이벤트 리스너 실행 | 아니요 | 아니요 (이미 롤백 완료됨) |
AFTER_COMPLETION | 트랜잭션이 커밋되었든 롤백되었든 무조건 실행 | 아니요 | 아니요 (트랜잭션에 영향 없음) |
예제:
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(CustomEvent event) {
System.out.println("트랜잭션 커밋 후 실행");
}
3.2 비동기 이벤트 처리와 트랜잭션
- @Async를 사용하면 이벤트 리스너가 비동기적으로 실행되어 트랜잭션과 완전히 분리됩니다.
- 이 경우, 리스너에서 발생한 예외는 부모 서비스로 전파되지 않으며, 트랜잭션에도 영향을 주지 않습니다.
@Async
@EventListener
public void handleAsyncEvent(CustomEvent event) {
System.out.println("비동기 이벤트 처리");
}
참고: 비동기 이벤트 리스너에서 발생한 예외는 기본적으로 무시됩니다. 필요 시 AsyncUncaughtExceptionHandler를 설정하여 예외를 처리할 수 있습니다.
4. 마무리
스프링 이벤트 시스템은 모듈 간 결합도를 낮추고, 비동기 처리와 같은 기능을 손쉽게 구현할 수 있는 강력한 도구입니다.
특히, 트랜잭션 전파와 예외 전달 개념이 처음에는 다소 복잡하게 느껴질 수 있지만, 이벤트를 적절히 활용하면 서비스 코드에서 여러 리포지토리를 직접 참조하지 않고도 필요한 작업을 깔끔하게 분리할 수 있습니다.
기존에는 하나의 서비스 안에서 여러 리포지토리를 호출하거나 중첩된 로직으로 코드가 복잡해지는 경우가 많았지만, 이벤트 시스템을 활용하면 이러한 복잡성을 줄이고, 책임을 분리하여 더 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.