반응형
SpringFramework 4.2 이후의 Spring 이벤트의 업데이트 사항을 알아보자
내가 구성한 MSA 프로젝트에서는 멤버 서버와 또 다른 서버가 상호 작용한다. 이 중, 유저가 닉네임을 변경할 때 멤버 서버의 닉네임 변경 메서드가 jpa를 통해 데이터베이스의 변경 사항을 커밋한다. 흥미로운 점은, DB 커밋 직후 비즈니스 로직을 종료하는 게 아니라 '유저 정보가 업데이트되었다'는 Spring 이벤트를 발행하고 종료한다. 그럼 이 이벤트를 구독하는 스프링 리스너가 AWS SNS에 "유저 정보가 업데이트되었다'는 메시지를 발행하도록 했다. 이러한 Spring 이벤트의 활용 방식에 대해 깊이 있게 이해하고자 Spring Framework 4.2 이후 추가된 기능들을 공식 페이지에서 직접 탐구해 보았다.
1. Generics 지원기능 추가
1-1. Generics를 이용한 이벤트 리스너 구현
- Spring Framework 4.2부터 ApplicationListener 인터페이스를 구현할 때 이벤트 타입에 제네릭(Generic) 정보를 포함할 수 있게 되었다. 이를 통해 특정 타입의 이벤트를 처리하는 리스너를 더 명확하게 정의할 수 있다.
public class MyEventHandler implements ApplicationListener<MyEvent<Order>> {
@Override
public void onApplicationEvent(MyEvent<Order> event) {
Order order = event.getData();
// 이벤트에 대한 처리 로직
}
}
- 이 예시에서 MyEventHandler는 MyEvent<Order> 타입의 이벤트를 처리한다. 제네릭을 활용함으로써 Spring Framework는 이벤트를 전송할 때 해당 이벤트를 처리할 이벤트 리스너(@EventListener)의 메서드 시그니처(메서드 이름과 파라미터 타입 등)를 기반으로 해당 이벤트와 일치하는 리스너를 정확하게 찾아 결정한다. 이는 타입 안전성을 보장하며 정확한 이벤트 처리를 가능하게 한다.
2. Annotation-driven 이벤트 리스너 기능 추가
2-1. Annotation-driven 이벤트 리스너의 기본 개념
- @EventListener 어노테이션을 관리된 빈(Bean)의 메서드에 달아서 자동으로 ApplicationListener를 등록할 수 있게 되었다. 이 어노테이션은 @Autowired와 유사하게 투명하게 처리되며, 자바 설정과 <context:annotation-driven/> 요소가 있으면 별도의 설정 없이 지원된다.
2-2. Annotation-driven 이벤트 리스너의 실제 사용 예시
- @EventListener 어노테이션을 사용한 예시는 다음과 같다. 이 어노테이션은 이벤트 리스너를 쉽게 정의할 수 있게 해 준다. 아래 예시에서 MyCustomEvent라는 이벤트를 처리하는 handleCustomEvent()라는 메서드를 만들었다. 이 메서드는@EventListener 어노테이션을 사용해서 이벤트 리스너로 자동으로 등록된다.
@Component
public class MyEventListener {
@EventListener
public void handleCustomEvent(MyCustomEvent event) {
// 여기에 이벤트 처리 로직을 작성
System.out.println("Received custom event - data: " + event.getData());
}
}
- 이 코드에서 handleCustomEvent() 메서드는 MyCustomEvent 타입의 이벤트가 발생할 때마다 자동으로 호출된다. @EventListener 어노테이션 덕분에 복잡한 설정 없이도 이 메서드가 이벤트 리스너로 동작하게 되는 것이다. 이 방식은 코드를 간결하게 유지하면서도 강력한 이벤트 처리 기능을 제공해 준다.
3. 이벤트 발행기능 추가
3-1. @EventListener 반환값을 새로운 이벤트로 변환
- Spring Framework 4.2부터 @EventListener 어노테이션을 작성한 메서드는 비-void 반환 타입을 가질 수 있게 되었다. 이는 메서드가 데이터(예: 문자열, 숫자, 객체 등)를 반환할 수 있다는 의미이다. 이벤트를 처리하고 나서 반환된 값은 새로운 이벤트로 자동 발행되며, 이를 통해 이벤트 처리 결과를 다른 리스너에게 전달할 수 있다. 이렇게 하면 하나의 이벤트 처리가 연쇄적인 다른 이벤트들을 발생시킬 수 있는 구조를 만들 수 있다.
- 이벤트를 처리한 후 반환된 값이 새로운 이벤트로 자동 발행된다는 의미는, @EventListener로 주석된 메서드가 이벤트를 처리하고 나서 어떤 값을 반환하면, 이 반환된 값이 자동으로 새로운 이벤트로 변환되어 발행된다는 것을 의미한다. 즉, 이벤트 처리 메서드가 결과값을 반환하면, 이 결과값은 다른 리스너가 반응할 수 있는 새로운 이벤트가 되는 것이다.
- 예를 들어, 만약 어떤 이벤트를 처리하는 메서드가 문자열을 반환한다면, 이 문자열은 새로운 이벤트로 자동 변환되어 다른 이벤트 리스너들에게 전달될 수 있다. 이를 통해 하나의 이벤트 처리가 연쇄적인 다른 이벤트들을 발생시킬 수 있는 구조를 만들 수 있다.
실제 코드 상황을 예시로 들어 설명하도록 하겠다.
- MyEventListener의 handleCustomEvent() 메서드는 MyEvent 이벤트를 처리하고 문자열(String) 타입의 결과를 반환한다. 이 반환된 문자열은 새로운 이벤트로 자동 발행된다.
@Component
public class MyEventListener {
@EventListener
public String handleCustomEvent(MyEvent event) {
// 이벤트 처리 로직
return "Result from " + event.getData();
}
}
- StringEventListener의 handleStringEvent() 메서드는 이 문자열 이벤트를 처리합니다.
@Component
public class StringEventListener {
@EventListener
public void handleStringEvent(String event) {
// 문자열 이벤트 처리 로직
System.out.println("Received string event: " + event);
}
}
- 이 연쇄적인 이벤트 처리가 가능한 이유는 Spring Framework가 @EventListener 메서드의 반환값을 새로운 이벤트로 간주하고, 해당 타입과 일치하는 다른 @EventListener 메서드를 찾아 실행하기 때문이다. 따라서, 문자열 타입의 이벤트가 발행되면, 문자열을 처리하는 모든 @EventListener 메서드가 실행될 것이다. 이렇게 하나의 이벤트 처리가 다른 이벤트들을 연쇄적으로 발생시킬 수 있다.
3-2. ApplicationEvent 확장 없이 커스텀 이벤트 발행
- 또한 Spring Framework4.2부터는 ApplicationEvent 클래스를 상속받지 않고도 임의의 객체를 이벤트로 사용할 수 있다. 이는 ApplicationEventPublisher 인터페이스가 모든 타입의 객체를 이벤트로 발행할 수 있도록 확장되었기 때문이다.
public class MyCustomEvent {
private final String data;
public MyCustomEvent(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
- 위의 MyCustomEvent 클래스는 ApplicationEvent를 상속받지 않은 단순한 클래스다. MyCustomEvent 클래스는 데이터를 저장하는 역할만 한다. 이 데이터는 이벤트와 관련된 어떤 정보를 담을 수 있으며, 이 클래스의 인스턴스는 ApplicationEventPublisher를 통해 이벤트로 발행될 수 있다. 발행된 이벤트는 Spring Framework에 의해 PayloadApplicationEvent로 감싸져서 처리되며, 이를 통해 이벤트 리스너들이 해당 이벤트를 처리할 수 있게 된다. 이러한 방식으로 Spring Framework는 이벤트 처리를 보다 유연하게 다룰 수 있게 되었다.
@Component
public class MyEventPublisher {
private final ApplicationEventPublisher publisher;
public MyEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void publishEvent() {
// 임의의 객체를 이벤트로 발행
MyCustomEvent event = new MyCustomEvent("Some data");
publisher.publishEvent(event);
}
}
- 이벤트를 발행할 때 ApplicationEventPublisher를 사용하여 이 객체를 발행하고, Spring은 이 객체를 PayloadApplicationEvent로 감싸서 처리한다. 이 변경으로 인해서 스프링 이벤트의 발행과 처리가 더 유연하고 다양해졌다.
4. 트랜잭션 바운드 이벤트 기능 추가
4-1. 트랜잭션 단계별 이벤트 리스너 바인딩
- Spring Framework 4.2부터는 이벤트 리스너를 트랜잭션의 특정 단계에 바인딩하는 기능이 추가되었다. 이 기능을 통해, 예를 들어 트랜잭션이 성공적으로 완료된 후에 특정 이벤트를 처리할 수 있게 되었다. 이는 트랜잭션의 결과가 리스너의 로직에 중요할 때 매우 유용하다.
- 이 기능을 구현하는 방법은 @TransactionalEventListener 어노테이션을 사용하는 것이다. 이 어노테이션을 이벤트 리스너 메서드에 적용하고, phase 속성을 통해 트랜잭션의 어떤 단계에서 이벤트를 처리할지 지정할 수 있다.
- 예를 들어, 트랜잭션이 커밋된 후(AFTER_COMMIT), 롤백된 후(AFTER_ROLLBACK), 트랜잭션 진행 중(BEFORE_COMMIT), 트랜잭션이 완료되기 전(BEFORE_COMPLETION)에 이벤트를 처리할 수 있다.
4-2. 트랜잭션 상황별 세밀한 이벤트 처리 전략
- AFTER_COMMIT
- 트랜잭션이 성공적으로 커밋된 후, 더 이상 필요하지 않은 리소스를 정리하거나 후속 작업을 수행한다.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(MyEvent event) {
// 리소스 정리 또는 후속 작업 로직
}
- AFTER_ROLLBACK
- 트랜잭션이 롤백된 경우, 해당 상황에 대한 로깅이나 오류 처리를 수행한다.
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(MyEvent event) {
// 에러 로깅 또는 예외 처리 로직
}
- AFTER_COMPLETION
- 트랜잭션 완료 후 처리 트랜잭션이 완전히 종료된 후에 필요한 작업을 수행한다. 이는 트랜잭션이 성공적으로 커밋되었든 롤백되었든 상관없이 실행된다.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(MyEvent event) {
// 트랜잭션 완료 후 최종 결과에 따른 로직 처리
}
- BEFORE_COMMIT
- 트랜잭션이 커밋되기 전에 필요한 작업을 수행한다. 이 단계에서는 데이터베이스에 반영되기 전에 마지막 검증이나 조정을 할 수 있다.
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(MyEvent event) {
// 트랜잭션 커밋 전 필요한 로직 처리
}
- BEFORE_COMPLETION
- 트랜잭션 완료 전 처리 트랜잭션이 완료되기 전에 필요한 작업을 수행한다. 이 단계는 트랜잭션의 성공 또는 실패 여부에 상관없이 실행됩니다.
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMPLETION)
public void beforeCompletion(MyEvent event) {
// 트랜잭션 완료 전 필요한 로직 처리
}
5. 업데이트에 대한 내용 출처 (스프링 공식 페이지)
이 내용은 하단의 스프링 공식 블로그에 나온 내용을 토대로 정리하였다.
이렇게 스프링 이벤트의 주요한(중요한) 업데이트 사항을 알아봤다. 나도 이번에 스프링 이벤트를 처음 사용해 봤는데 별거 아닌 것 같지만 편의성을 위한 업데이트가 진행되었던 것 같다.
스프링 공식 블로그의 글이 2015년인 것을 보아 상당히 오래전에 진행된 업데이트인데 이전에는 ApplicationEvent를 상속받아서 스프링 이벤트 클래스를 생성했던 기록이 많이 남아있어서 그런지 GPT에게 물어보거나 다른 글을 봐도 대부분 수정되었지만 아직 몇몇 글에서는 ApplicationEvent를 상속받는 형식을 사용하라는 글들이 존재했다. 기술은 항상 새로운 버전으로 진화하기에 바로 따라 하기보단 이렇게 어떤 방식으로 업데이트되었는지 아는 것 또한 중요하다는 생각이 든다.
위에서 설명한 기능들을 아래의 글에서 사용하기 위함이었다. 시간이 난다면 글을 읽어보는것을 추천한다!
반응형
'Spring MSA' 카테고리의 다른 글
[Spring] Util 클래스 - static vs Bean (3) | 2023.11.18 |
---|---|
[Spring MSA] Zipkin으로 분산추적 로깅 구현하기 (0) | 2023.11.18 |
[Spring MSA] 스프링 이벤트와 SNS/SQS로 DB 정합성 보장 2탄 - ZeroPayload로 FeignClient 요청 (0) | 2023.11.17 |
[Spring MSA] Spring Event, SNS, SQS를 사용하여 DB 정합성 보장하기 1탄 (2) | 2023.11.15 |
Spring - 트랜잭션 관리 (Transaction Management) (0) | 2023.08.08 |