Post

(Spring) EventListener

https://www.baeldung.com/spring-events

  • ApplicationEventPublisher::publishEvent로 pub 하고
  • @EventListener로 event 받아 처리하면 된다.
  • 단 여기서 주의해야 할점! listener가 이벤트를 수신하는 것이 왠지 비동기로 이루어질 것 같지만, 기본적으로 동기식이다!
    • 한 스레드가 pub한 다음, 해당 이벤트를 처리해야 하는 EventListener들을 돌면서 동기식으로 직접 리스너를 수행한다.
    • By default, the listener is invoked synchronously. However, we can easily make it asynchronous by adding an @Async annotation. We just need to remember toenableAsyncsupportin the application.

Event와 순환 참조 문제

  • Event를 사용하면 의존성을 끊을 수 있지만, 이렇게 의존성을 끊고 나서 반대방향 참조를 하게 되면 아래와 같은 에러를 마주칠 수 있다.
1
2
3
22:10:41.056 [WebSocketClient-SecureIO-2] ERROR o.s.w.s.a.s.StandardWebSocketHandlerAdapter - Closing session due to exception for StandardWebSocketSession[id=b85eba15-6fc8-6b1f-1c6d-bb93a2ccb1e7, uri=null]
org.springframework.beans.factory.BeanCreationNotAllowedException:
    Error creating bean with name 'myContainer': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)

원인?

  • 종료 시, Listener가 Publisher를 참조하고 있으므로, Listener bean이 먼저 destroy 된다.
  • Publisher가 Event를 publish하면, 이벤트를 수신할 bean을 가져오게 되는데, 이 때 최종적으로 DefaultSingletonBeanRegistry.getSingleton를 사용하여 가져오게 된다. 이 메서드는 싱글턴을 반환하는데 없으면 생성 시도한다.
  • Listener는 이미 destroy 되었으므로 생성 시도할텐데, shutdown으로 destruction phase에 진입했기 때문에 새로운 빈을 생성할 수 없다. 따라서 에러 발생하는 것.
1
2
3
4
5
6
7
8
9
10
11
/**
  * Return the (raw) singleton object registered under the given name,
  * creating and registering a new one if none registered yet.
  */
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName, ...

해결방안?

  • 무시하거나, (어차피 종료 시에만 뜬다.)
  • try-catch 처리하거나,
  • application이 종료될 때, shutdown hook을 이용해서 flag 변수를 set하고 이를 체크해서 publish하는 식으로 동작하거나. (✓)

도메인 모델에서 이벤트 직접 발행

Event는 언제 유용한가?

  • 가격 조회 local container가 갱신 될 때 마다 특정 작업을 호출해야 하는데, 가격 조회 관련 기능이 별도 client module로 분리되어 있는 경우.
  • 모듈 참조 방향 때문에 client module에서는 갱신 시 호출 될 작업들에 대해서는 알 수 없다.
  • 이런 경우 client module에서는 event 발행하고, 나머지 비즈니스 모듈에서 event 구독하는 방식으로 처리하게 됨.
This post is licensed under CC BY 4.0 by the author.