모종닷컴

Spring EventListener do not working 본문

Programming/Spring

Spring EventListener do not working

모종 2025. 10. 11. 19:48
반응형
@Component
class Test(...) {
    init {
        eventPublihser.publishEvent(TestEvent())
    }
}


@Service
class TestService {
  @EventListener(TestEvent::class)
  fun handleEvent(event: TestEvent) {
      log.info { "hello" }
  }
}

이슈

위와 같은 코드를 작성했을 때 애플리케이션이 켜지면 “hello”가 출력되기를 기대했으나 출력이 안됨.

반면 API를 하나 만들어서 동일한 코드를 실행하면 Listener에서 감지가 된다.

원인

/** Class SimpleApplicationEventMulticaster **/

    @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);
            }
        }
    }
  • 애플리케이션 init 에서 publishEvent와 api로 publishEvent 한 것이 어떤 차이가 나는지를 확인해보기 위해 디버깅을 해보면 위 동작이 달라진다.
  • 애플리케이션 시작시 publishEvent 호출은 for문의 getApplicationListeners(event, type) 결과는 0이고 API로 호출했을 때는 1이 나오면서 invoke(listener, event)가 실행된다.
  • 위 코드는 특별한 로직이 아니다. 이벤트를 처리할 수 있는 리스너를 리턴하면 해당 리스너들에게 이벤트를 전달하는 로직이다.
  • init {} 코드가 실행되는 시점에는 리스너가 없었다가 api 호출 시점에는 리스너가 생겼다??
  • 여기서 해답을 찾았는데 init이 실행된 타이밍에 TestService의 listener 등록이 늦게 된 것이다.
/** Class AbstractApplicationEventMulticaster **/
    @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        synchronized (this.defaultRetriever) {
            // Explicitly remove target for a proxy, if registered already,
            // in order to avoid double invocations of the same listener.
            Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
            if (singletonTarget instanceof ApplicationListener) {
                this.defaultRetriever.applicationListeners.remove(singletonTarget);
            }
            this.defaultRetriever.applicationListeners.add(listener);
            this.retrieverCache.clear();
        }
    }
  • 리스너를 조회하는 코드를 타면 결국 this.defaultRetriever.applicationListeners 로부터 가져오는데 코드 마지막쯤 add코드가 보인다. 이 라인에 브레이크포인트를 걸고 condition을 입력한다.
    • listener.getClass() == ApplicationListenerMethodAdapter.class && ((ApplicationListenerMethodAdapter) listener).beanName.equals("")
  • 애플리케이션을 실행해보면 init {..} 코드가 먼저 실행되고 이후에 listeners.add 브레이크 포인트가 동작한다.
반응형