이벤트 리스너 등록
@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 정리 (1) | 2025.01.02 |
| @EnableWebMvc를 붙이지 않은 이유 (3) | 2024.09.23 |
| Spring에서의 Filter 간단 정리 (4) | 2024.09.19 |
| SpringBoot에서 HTTP 요청을 처리하는 과정을 살펴보며 (DispatcherServlet) (1) | 2024.08.26 |