이벤트란 무엇인가?
Spring 프레임워크에서 이벤트는 ApplicationContext가 제공하는 기능 중 하나로, 특정 상황에서 이벤트를 발행(publish)하고 이를 처리할 수 있도록 지원합니다. 이 기능은 크게 아래 세 가지로 구성됩니다.
- 이벤트(Event)
- 발행자(Publisher)
- 리스너(Listener)
1. Event (이벤트)
Spring은 사용자 정의 이벤트를 생성하고 발행할 수 있도록 지원합니다. 기본적으로 이러한 이벤트는 동기식으로 동작합니다.
public class CustomSpringEvent extends ApplicationEvent {
private String message;
public CustomSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
2. 발행자 (Publisher)
발행자는 이벤트 객체를 생성하고, 이를 듣고 있는 모든 리스너에게 발행합니다.
ApplicationEventPublisher 사용
발행자는 ApplicationEventPublisher를 주입받아 publishEvent() 메서드를 통해 이벤트를 발행할 수 있습니다.
@Component
public class CustomSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publishCustomEvent(final String message) {
System.out.println("Publishing custom event. ");
CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
applicationEventPublisher.publishEvent(customSpringEvent);
}
}
대안으로, ApplicationEventPublisherAware 인터페이스를 구현하여 사용할 수도 있습니다.
3. 리스너 (Listener)
리스너는 특정 이벤트를 처리하기 위해 등록됩니다. 이를 구현하기 위해서는 해당 클래스를 Spring 빈(Bean)으로 등록하고, ApplicationListener 인터페이스를 구현해야 합니다.
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}
- 작성된 리스너는 제네릭 타입으로 매개화되어 있으며, 이를 통해 타입 안전성을 제공합니다.
- 타입 확인이나 형변환을 할 필요가 없습니다.
비동기식 이벤트
기본적으로 이벤트는 동기식으로 처리됩니다. 하지만 경우에 따라 비동기적으로 처리해야 할 때가 있습니다.
비동기 처리를 활성화하려면 Executor를 가진 ApplicationEventMulticaster Bean으로 생성하여 설정에서 이를 활성화할 수 있습니다.
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
이 설정 후, 리스너는 비동기적으로 이벤트를 처리하게 됩니다. 이벤트, 발행자, 리스너의 구현 방식은 기존과 동일하게 유지됩니다.
어노테이션을 활용한 이벤트 처리
Spring 4.2부터는 리스너를 구현할 때 반드시 ApplicationListener 인터페이스를 상속받을 필요가 없어졌습니다. 대신, 메서드에 @EventListener 애너테이션을 추가해 리스너로 사용할 수 있습니다.
@Component
public class AnnotationDrivenEventListener {
@EventListener
public void handleContextStart(ContextStartedEvent cse) {
System.out.println("Handling context started event.");
}
}
메서드 시그니처를 통해 어떤 이벤트를 처리할지 결정합니다. 기본적으로 동기식으로 동작하지만, @Async 애너테이션을 추가하면 비동기적으로 처리할 수 있습니다. 단, 애플리케이션에서 비동기 처리를 활성화해야 합니다.
제네릭 타입을 활용한 이벤트
Spring은 제네릭 타입을 활용해 이벤트와 리스너를 보다 유연하게 설계할 수 있습니다.
또한, ApplicationEvent를 상속하지 않고도 이벤트를 정의할 수 있습니다.
public class GenericSpringEvent<T> {
private T what;
protected boolean success;
public GenericSpringEvent(T what, boolean success) {
this.what = what;
this.success = success;
}
// ... standard getters
}
제네릭 리스너
@Component
public class GenericSpringEventListener
implements ApplicationListener<GenericSpringEvent<String>> {
@Override
public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
System.out.println("Received spring generic event - " + event.getWhat());
}
}
조건부 이벤트 핸들링
@EventListener 애너테이션에 조건을 추가하여 특정 조건을 만족할 때만 이벤트를 처리할 수 있습니다.
@Component
public class AnnotationDrivenEventListener {
@EventListener(condition = "#event.success")
public void handleSuccessful(GenericSpringEvent<String> event) {
System.out.println("Handling generic event (conditional).");
}
}
여기서 #event.success는 SpEL(Spring Expression Language)을 활용한 표현식입니다.
success 필드가 true인 경우에만 메서드가 호출됩니다.
제네릭 발행자(Generic Publisher)
제네릭 이벤트 발행자는 일반적인 이벤트 발행 방식과 유사하지만, 타입 소거(Type Erasure)로 인해 제네릭 파라미터를 해결할 수 있도록 별도의 구현이 필요합니다. 예를 들어, 특정 타입의 제네릭 이벤트를 발행하려면 다음과 같이 구현할 수 있습니다.
public class GenericStringSpringEvent extends GenericSpringEvent<String> {
public GenericStringSpringEvent(String what, boolean success) {
super(what, success);
}
}
대안적인 이벤트 발행 방법
Spring에서는 이벤트 발행을 더 유연하게 처리하기 위한 대안적인 방법을 제공합니다. 이를 활용하면 메서드에서 이벤트 객체를 반환하여 이벤트를 발행할 수 있습니다.
@EventListener를 통한 새로운 이벤트 발행
@EventListener로 주석이 달린 메서드가 null이 아닌 값을 반환하면, Spring은 반환된 객체를 새로운 이벤트로 간주하고 다시 발행합니다.
@Component
public class AnnotationDrivenEventPublisher {
@EventListener
public GenericSpringEvent<String> handleAndPublish(String input) {
System.out.println("Handling input and publishing a new event.");
return new GenericSpringEvent<>(input, true);
}
}
위의 예제에서 메서드가 GenericSpringEvent 객체를 반환하면, Spring은 해당 객체를 새로운 이벤트로 발행합니다.
Transaction Bound Events
Spring 4.2부터, 프레임워크는 새로운 @TransactionalEventListener 애너테이션을 제공합니다.
이는 @EventListener의 확장으로, 이벤트 리스너를 트랜잭션의 특정 단계에 바인딩할 수 있게 해줍니다.
바인딩은 다음 트랜잭션 단계에 가능합니다:
- AFTER_COMMIT (기본값): 트랜잭션이 성공적으로 완료된 후 이벤트를 발생시킵니다.
- AFTER_ROLLBACK: 트랜잭션이 롤백된 경우 이벤트를 발생시킵니다.
- AFTER_COMPLETION: 트랜잭션이 완료된 경우 이벤트를 발생시킵니다.
(AFTER_COMMIT 및 AFTER_ROLLBACK의 별칭) - BEFORE_COMMIT: 트랜잭션 커밋 직전에 이벤트를 발생시킵니다.
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}
이 리스너는 이벤트 프로듀서가 실행 중인 트랜잭션이 있고, 커밋되려는 경우에만 호출됩니다.
그리고 트랜잭션이 실행 중이지 않다면, fallbackExecution 속성을 true로 설정하지 않는 한 이벤트는 전혀 전송되지 않습니다.
참조
https://www.baeldung.com/spring-events
https://newwisdom.tistory.com/75
ApplicationEventPublisher 적용과 그 안에서의 삽질
ApplicationEventPublisher 이벤트 핸들러는 이벤트 생성 주체가 발생한 이벤트에 반응하고, 이벤트 핸들러는 생성 주체가 발행한 이벤트를 전달받아 이벤트에 담긴 정보(데이터)를 기반으로 해당 기능
newwisdom.tistory.com
'Spring' 카테고리의 다른 글
| [KAFKA] Spring Boot로 KAFKA 사용해보기 (0) | 2025.04.02 |
|---|---|
| [해결 방안] Spring STOMP content-length 초과 에러 해결하기 (0) | 2025.03.31 |
| [해결 방안] Spring Gateway를 통해 Stomp를 설정했을때 헤더가 두개오는 문제 해결방안 (1) | 2025.03.31 |
| [Spring] MongoDB와 JPA Repository 충돌 해결 (1) | 2025.02.09 |
| Spring Events을 활용하여 결합도 낮추기 (5) | 2024.12.23 |