Spring Framework 4.2 이후 버전에서 스프링 이벤트 적용하기
Spring Boot 3 버전에서 내부 로직 처리를 위해 Spring event를 사용해야 했다. 찾아보니 Spring Boot 3 버전에서 적용할 Spring event가 이전 버전과 꽤 다르다는 점을 알게 되어서 정리해 봐야겠다.
스프링 프레임워크에서 스프링 이벤트 처리 방식이 업데이트되면서 주요 변경 사항이 몇 가지 있다. 이러한 변경 사항은 개발자가 이벤트를 더 유연하고 효율적으로 다룰 수 있게 해 준다. 아래에서 이러한 차이점과 적용 방법을 자세히 설명하겠다.
4.2 버전 이전, 스프링 이벤트
스프링 프레임워크 4.2 버전에서의 스프링 이벤트 처리는 상당히 기본적이었다.
이때, 개발자들은 ApplicationEvent 클래스를 상속받아 사용자 정의 이벤트를 만들고, ApplicationEventPublisher를 통해 이벤트를 발행했다.
이벤트 리스너는 ApplicationListener 인터페이스를 구현하거나 @EventListener 어노테이션을 사용하여 이벤트를 처리했다.
스프링 4.2 이전 버전의 이벤트 처리 예시:
public class CustomEvent extends ApplicationEvent {
public CustomEvent(Object source) {
super(source);
}
}
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
// 이벤트 처리 로직
}
}
4.2 버전 이후, 스프링 이벤트
4.2 버전 이후, 스프링 프레임워크는 이벤트 처리 기능을 향상시켰다. 주요 변경 사항은 다음과 같다:
- 제네릭 Generic 지원
- Annotation 기반 이벤트 리스너
- 이벤트 발행
- 트랜잭션에 바인딩된 이벤트
1. 제네릭 Generic 지원
- 제네릭의 도입: 이전에는 ApplicationListener를 구현할 때 모든 종류의 이벤트에 대해 같은 리스너를 사용해야 했다. 그러나 4.2부터는 제네릭을 사용하여 특정 타입의 이벤트에 대한 리스너를 만들 수 있다.
- 특정 이벤트 타입 리스너: 예를 들어, MyEvent<Order> 타입의 이벤트만 처리하는 리스너를 만들 수 있다. 이는 Order 타입 관련 이벤트에만 반응하도록 리스너를 좁혀준다.
- 이벤트 디스패치와 일치: 이벤트가 발생하면, 스프링은 이벤트 타입과 리스너의 시그니처(정의된 메소드의 타입)를 비교한다. 예를 들어, MyEvent<Order> 타입의 이벤트가 발생하면, 이 타입을 처리할 수 있는 리스너만 호출된다.
즉, 이 변경사항은 개발자가 더 특정하고 세밀한 이벤트 처리 리스너를 작성할 수 있게 해 주어, 코드의 명확성과 효율성을 높여준다.
1-1. 예시 코드 1
public class MyListener implements ApplicationListener<MyEvent<Order>> {
// ... 기타 메소드 구현
}
- MyListener 클래스는 제네릭을 사용하여 MyEvent<Order> 타입의 이벤트를 처리하는 ApplicationListener 구현체다.
- 이는 Spring Framework 4.2에서 새롭게 추가된 기능으로, 이전 버전에서는 이런 방식의 제네릭 지원이 없었다.
1-2. 예시 코드 2
public class MyOrderEvent extends MyEvent<Order> {
// ... 이벤트에 관련된 메소드와 속성
}
- MyOrderEvent 클래스는 MyEvent<Order>를 확장한 이벤트 클래스이다.
- 이 클래스 자체는 4.2 이전 버전에서도 사용할 수 있는 일반적인 클래스 확장이지만, 이를 ApplicationListener<MyEvent<Order>> 리스너에서 사용하는 것은 4.2의 변경사항을 활용하는 예시다.
2. 어노테이션 기반 이벤트 리스터
- 이제 @EventListener 어노테이션을 사용하여 메소드를 자동으로 ApplicationListener로 등록할 수 있다.
- 이는 기존의 명시적인 인터페이스 구현 대신, 간단한 어노테이션을 통해 이벤트 리스너를 생성할 수 있게 해 준다.
@Component
public class MyListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
// ... 이벤트 처리 로직
}
}
- 또한, SpEL 표현식을 사용하여 특정 조건을 충족할 때만 이벤트를 처리하도록 할 수 있다.
- 예를 들어, awesome이라는 플래그가 참일 때만 작동하는 이벤트 리스너를 구현할 수 있다.
// Order 생성 이벤트 클래스
public class OrderCreatedEvent implements CreationEvent<Order> {
private boolean awesome;
public boolean isAwesome() {
return this.awesome;
}
// ... 기타 메소드 및 속성
}
@Component
public class MyComponent {
// awesome 조건이 참일 때만 이 이벤트 리스너를 호출
@EventListener(condition = "#creationEvent.awesome")
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
// ... 이벤트 처리 로직
}
}
- 여기서 #creationEvent.awesome은 SpEL 표현식으로, 이벤트 객체의 awesome 속성이 참일 때만 handleOrderCreatedEvent 메소드가 호출된다.
- 이를 통해 개발자는 보다 세밀하게 이벤트 리스너의 동작 조건을 제어할 수 있다.
💡 #creationEvent.awesome이 무슨 뜻일까?
#creationEvent는 @EventListener 어노테이션이 달린 메소드의 인자 이름을 참조한다.
이 코드에서 handleOrderCreatedEvent(CreationEvent<Order> creationEvent) 메소드에 CreationEvent<Order> 타입의 인자가 있는데, 이 인자의 이름이 creationEvent이다.
SpEL(스프링 표현 언어)을 사용하여 condition = "#creationEvent.awesome"이라고 정의함으로써, 이 메소드는 creationEvent 인자의 awesome 속성이 참일 때만 호출되도록 설정된다.
즉, #creationEvent는 메소드의 인자를 참조하고, .awesome은 그 인자의 isAwesome() 메소드의 결과를 사용하는 것이다.
3. 이벤트 발행
- 이벤트 리스너 메소드에서 void가 아닌 반환 타입을 사용하여, 특정 이벤트 처리의 결과를 새로운 이벤트로 전송할 수 있다.
- 또한, 임의의 객체를 이벤트로 발행할 수 있는 유연성도 제공한다.
- 아래 코드는 SomeEvent를 처리하고 새로운 MyEvent<Order> 이벤트를 반환하는 예시 코드다.
@EventListener
public MyEvent<Order> handleSomeEvent(SomeEvent event) {
// ... 이벤트 처리 로직
return new MyEvent<>(new Order()); // 새로운 이벤트 반환
}
4. 트랜잭션에 바인딩된 이벤트
- 새로운 @TransactionalEventListener 어노테이션을 사용하여, 이벤트 리스너를 트랜잭션의 특정 단계에 바인딩할 수 있다.
- @TransactionalEventListener는 @EventListener와 유사하게 동작하며, 트랜잭션의 여러 단계(기본적으로 AFTER_COMMIT)에서 이벤트를 처리할 수 있다.
- 가능한 트랜잭션 단계는 아래와 같다.
- AFTER_COMMIT
- BEFORE_COMMIT
- AFTER_ROLLBACK
- AFTER_COMPLETION (=AFTER_COMMIT 및 AFTER_ROLLBACK의 별칭)
- 트랜잭션이 없는 경우 기본적으로 이벤트가 전송되지 않지만, @TransactionalEventListener의 fallbackExecution 속성을 사용하여 트랜잭션이 없는 경우 즉시 리스너를 호출할 수 있다.
- 이러한 어노테이션은 트랜잭션 수명주기 내에서 이벤트 처리 메소드가 언제 실행되는지를 제어한다.
4-1. AFTER_COMMIT
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommitEventHandler(MyEvent event) {
// 이 메소드는 트랜잭션이 성공적으로 커밋된 후에 호출됩니다.
// 트랜잭션이 커밋된 이후에 실행되어야 하는 작업을 수행합니다.
}
4-2. BEFORE_COMMIT
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommitEventHandler(MyEvent event) {
// 이 메소드는 트랜잭션이 커밋되기 전에 호출됩니다.
// 실제 커밋 이전에 작업 또는 유효성 검사를 수행하는 데 사용합니다.
}
4-3. AFTER_ROLLBACK
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollbackEventHandler(MyEvent event) {
// 이 메소드는 트랜잭션이 롤백된 후에 호출됩니다.
// 트랜잭션 롤백 시 필요한 정리 또는 로깅을 처리합니다.
}
4-4. AFTER_COMPLETION
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletionEventHandler(MyEvent event) {
// 이 메소드는 트랜잭션이 완료된 후(커밋 또는 롤백)에 호출됩니다.
// 어떤 경우에도 발생해야 하는 후속 트랜잭션 완료 작업에 적합합니다.
}
5. 마무리
이번 글에서는 스프링 프레임워크 4.2 이후 버전에서 스프링 이벤트에 새롭게 업데이트된 내용을 다뤄봤다.
다음 글에서는 이 개념을 실제로 프로젝트에 어떻게 적용했는지 작성해 보겠다. 그리고 이를 통해 스프링 애플리케이션에서 이벤트 기반 아키텍처를 어떻게 구축하는지 확인해 보면 좋겠다.
(스프링 공식 페이지 참조: https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2)
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] MSA 환경에서 SNS/SQS 활용하기: 서버간 DB 동기화와 제로 페이로드 방식의 효과적 구현 (0) | 2023.11.18 |
---|---|
[Spring Boot]MSA 환경에서 SNS/SQS를 활용한 이벤트 처리: 이벤트 유실 문제 해결 방안 (0) | 2023.11.17 |
[Spring Boot] 스프링 부트에서 파라미터화된 로깅{}과 String.format 사용법 (0) | 2023.11.15 |
[Spring Boot] AOP를 활용한 로깅 구현: @EnableAspectJAutoProxy와 사용자 정의 Aspect 클래스 비교 가이드 (1) | 2023.11.13 |
[Spring Boot] Quartz 적용하기 (2) | 2023.11.11 |