이벤트 리스너 등록
@EventListener
@EventListener
public void onApplicationEvent(MyEvent event) {
// doSomething
}
위와 같이 @EventListener
로 등록된 이벤트 리스너는 EventListenerMethodProcessor
에 의해 컨텍스트에 ApplicationEventListener
로 등록된다.
public class EventListenerMethodProcessor
implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
// ...
@Override
public void afterSingletonsInstantiated() {
// ...
processBean(beanName, type);
}
private void processBean(final String beanName, final Class<?> targetType) {
// EventListener 어노테이션이 선언되어 있고 Spring Container에 속한 클래스인지 확인한다.
if (!this.nonAnnotatedClasses.contains(targetType) &&
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
!isSpringContainerClass(targetType)) {
// 어노테이션이 선언된 메서드를 찾는다.
Map<Method, EventListener> annotatedMethods = null;
try {
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
} catch (Throwable ex) {
// ...
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
// 어노테이션이 선언된 메서드가 없으면 아무것도 할 수 없다.
} else {
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
List<EventListenerFactory> factories = this.eventListenerFactories;
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 어노테이션이 선언된 메서드를 기준으로 applicationListener를 생성하여 컨텍스트에 등록한다.
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter alma) {
alma.init(context, this.evaluator);
}
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
}
}
EventListenerMethodProcessor
는 SmartInitializingSingleton
를 구현한다.
SmartInitializingSingleton
는 싱글톤 사전 인스턴스화 단계가 끝날 때 트리거되는 콜백 인터페이스로 afterSingletonsInstantiated
이 호출되는 시점엔 모든 일반 싱글톤 빈이 이미 생성되었다는 것을 보장한다.
이를 바탕으로 EventListener
어노테이션이 선언되어 있고 Spring Container에 속한 클래스인지 확인한 이후 메서드를 기준으로 EventListenerFactory
에서 applicationListener
를 생성하여 컨텍스트에 등록한다.
ApplicationListener 구현
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
ApplicationListener
를 구현하여 빈으로 등록하면 ApplicationListenerDetector
라는 BeanPostProcessor
에서 해당 빈을 컨텍스트에 등록한다.
class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// ApplicationListener 타입의 빈만 처리한다.
if (bean instanceof ApplicationListener<?> applicationListener) {
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// applicationContext에 applicationListener를 등록한다.
this.applicationContext.addApplicationListener(applicationListener);
}
else if (Boolean.FALSE.equals(flag)) {
// ...
}
}
return bean;
}
}
@EventListener
을 사용하는 방법과 비교하여 ApplicationListener
를 구현하는 방법의 단점은 아래와 같다.
ApplicationEvent
를 상속한 이벤트만 처리할 수 있다
@EventListener가 ApplicationEvent
를 상속하지 않은 이벤트 클래스를 처리할 수 있는 이유
Spring 4.2 부터 이벤트 리스너가 반드시 ApplicationEvent
를 상속한 클래스만 처리할 필요가 없어졌다.
이는 EventListenerMethodProcessor
에서 @EventListener
로 선언된 이벤트 리스너를 생성하는 과정을 확인해 보면 알 수 있다.
EventListenerMethodProcessor
가 @EventListener의 ApplicationListener를 생성하는 과정
EventListenerFactory
의createApplicationListener
메소드를 사용한다.
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
DefaultEventListenerFactory
의createApplicationListener
에서는ApplicationListenerMethodAdapter
를 생성한다.
@Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodAdapter(beanName, type, method);
}
ApplicationListenerMethodAdapter
가 생성되며 이벤트 리스너가 어떤 객체를 처리하는지 파악할 수 있다.
public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
// ...
this.method = BridgeMethodResolver.findBridgedMethod(method);
EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
// ...
}
private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) {
// ...
// ResolvableType.forMethodParameter(method, 0)를 통해 EventListener이 어떤 객체를 처리해야하는지 파악한다.
return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));
}
이벤트 처리
우선 스프링에서 이벤트 발행은 ApplicationEventPublisher
를 통해서 발행할 수 있다.
발행된 이벤트는 ApplicationEventMulticaster
에 전달된다.ApplicationEventMulticaster
는 ApplicationListener
객체를 관리하고 그들에게 이벤트를 발행하기 위한 인터페이스이다.
ApplicationEventPublisher
의 publishEvent
메서드를 사용하면 내부의 ApplicationEventMulticaster
에게 이벤트를 전달한다.
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
// ...
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else if (this.applicationEventMulticaster != null) {
// ApplicationEventMulticaster에게 이벤트를 전달한다.
this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
}
// ...
}
ApplicationEventMulticaster
에게 전달된 이벤트는 taskExecutor
설정 여부에 따라 동기/비동기적으로 이벤트를 처리한다.
이때 주의할 점은 해당 설정은 모든 이벤트 리스너에 일괄적으로 동기/비동기적으로 이벤트를 처리를 설정한다는 점이다.
기본 설정은 taskExecutor
이 설정되어 있지 않아 동기적으로 이벤트를 처리한다.
@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null && listener.supportsAsyncExecution()) {
try {
executor.execute(() -> invokeListener(listener, event));
}
catch (RejectedExecutionException ex) {
// Probably on shutdown -> invoke listener locally instead
invokeListener(listener, event);
}
}
else {
invokeListener(listener, event);
}
}
}
개별 이벤트 리스너의 이벤트 처리에 대한 동기/비동기 설정을 위해서는 해당 이벤트 처리 메서드에서 @Async
를 통해 설정할 수 있다.
'스프링' 카테고리의 다른 글
BeanPostProcessor (1) | 2025.01.16 |
---|---|
@Async 정리 (0) | 2025.01.02 |
@EnableWebMvc를 붙이지 않은 이유 (0) | 2024.09.23 |
Spring에서의 Filter 간단 정리 (2) | 2024.09.19 |
SpringBoot에서 HTTP 요청을 처리하는 과정을 살펴보며 (DispatcherServlet) (0) | 2024.08.26 |