본문 바로가기
💊 Java & Kotlin & Spring/- Spring

[Spring] ApplicationEventPublisher 를 이용해서 Event 를 발행하면 어떤 일이 일어날까

by Wonit 2022. 8. 15.

해당 글의 전체 소스코드는 github 에서 확인할 수 있습니다. README 를 참조하세요

 

목차

  • 동기
  • There is No Magic in spring

 

동기

 

최근에 DDD 를 학습하면서 도메인 이벤트라는 것을 알게 되었다.

 

그러면서 Spring 에서 한 VM 안에서 이벤트를 발행하고 해당 이벤트를 handling 하는 코드를 접했고, 그와 관련해서 여러 실습을 해보던 중 Spring 에서 무언가 Magic 이 일어나고 있는것 같은 느낌을 받았다.

 

긴 말 필요 없이 아래의 코드를 확인해보자.

 

@Entity(name = "orders")
public class Order {

    public static Order create() {
        return new Order();
    }

    @Id
    private Long id;
    private OrderStatus status = CREATED;

    public void cancel() {
        OrderCanceled event = OrderCanceled.of(id);
        Events.raise(event); // event trigger
        status = CANCELED;
    }
}

 

위의 코드는 Order 라는 실습을 위해 탄생한 가상의 도메인이다.

 

실습의 시나리오에서는 아주 간단한 로직만을 가지고 있다.

 

 

주문이 취소되면 Membership 의 상태를 변경하는 것이 다다.

 

해당 Order 라는 가상의 도메인 코드에서 집중해야 할 부분은 cancel() 이라는 메서드가 하는 일이다.

 

Events 라는 유틸리티성 클래스의 static 메서드인 raise() 를 호출해서 이벤트를 발행한다.

 

Events 클래스를 확인해보자

 

public class Events {
    private static ApplicationEventPublisher publisher;

    protected static void setPublisher(ApplicationEventPublisher publisher) {
        Events.publisher = publisher; // initializing
    }

    public static void raise(DomainEvent event) {
        if (publisher == null) {
            throw new IllegalStateException();
        }
        publisher.publishEvent(event);
    }
}

 

Events 클래스는 Spring 의 EventDispatcher 인 ApplicationEventPublisher 를 감싸고 있는 일종의 Facade 역할을 수행한다.

 

해당 코드에서는 raise() 를 호출하게 된다면 멤버로 가지고 있는 ApplicationEventPublisher 와 협력하여 event 를 publish 한다.

 

ApplicationEventPublisher 는 스프링에서 제공하는 event publisher 인데, Events 라는 클래스에서는 사용성을 높이기 위해서 static 으로 EventPublisher 를 외부에서 주입받는다.

 

@Configuration
@RequiredArgsConstructor
public class EventsConfig {
    private final ApplicationContext applicationContext;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Events.setPublisher(applicationContext);
    }
}

 

Events 를 초기화 하기 위해서 InitializingBean 을 사용했다.

 

자.

 

이런 저런 과정들을 해주고 해당 이벤트를 받고 있는 event handler 로 가보자

 

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderCanceledEventHandler {
    private final MembershipTerminateService membershipTerminateService;

    @EventListener(OrderCanceled.class)
    public void handle(OrderCanceled event) {
        log.info("OrderCanceledEvent occurred !! => {}", event);
        membershipTerminateService.terminateBy(event.getOrderId());
    }
}

@EventListener 어노테이션을 추가하면 해당 이벤트가 발행될 때 Listening 하여 특정 로직을 수행할 수 있도록 한다.

어떠한 마법이 숨어있는 것일까?

 

There is No Magic in spring

 

늘 그렇듯 There is No Magic in spring 이다.

 

내가 궁금한 것은 우선

 

  1. 왜 Config 에서 ApplicationContext 를 Events 에게 set 하는가?
  2. @EventListener 는 어떻게 consuming 하는가

 

인데, 하나씩 확인해보자.

 

왜 Config 에서 ApplicationContext 를 Events 에게 set 하는가?

 

EventPublisher 를 동적으로 set 해주는 Config 코드에서 ApplicationContext 를 넘겨서 Events 클래스에게 ApplicationEventPublisher 를 설정해준다.

 

@Configuration
@RequiredArgsConstructor
public class EventsConfig {
    private final ApplicationContext applicationContext;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Events.setPublisher(applicationContext);
    }
}

 

Events 가 원하는 멤버는 ApplicationEventPublisher 인데 왜 ApplicationContext 를 넘길까?

 

public class Events {
    private static ApplicationEventPublisher publisher;

    protected static void setPublisher(ApplicationEventPublisher publisher) {
        Events.publisher = publisher;
    }

    public static void raise(DomainEvent event) {
        //...
    }
}

 

우리가 알고 있는 ApplicationContext 는 IoC Container 로서 Bean Factory 역할을 수행한다.


그런 ApplicationContext 에는 다양한 기능이 존재하는데, 그중 하나가 바로 ApplicationEventPublishing 이다.

 

 

실제 스프링의 코드를 확인하더라도 동일하다

 

 

그리고 해당 ApplicationEventPublisher 의 구현은 ApplicationContext 를 상속하고 있는 AbstractApplicationContext 에서 존재한다.

 

결국 Events 에게 raise() 라는 협력을 요청하게 되면 AbstractApplicationContext 에 존재하는 publishEvent() 가 호출되게 된다.

 

Event 가 Publish 되면 Spring 에서 제공하는 ApplicationEvent 타입인지 먼저 확인하고 그렇지 않다면, Custom 한 Event 라면 ApplicationEvent 의 구현체인 PayloadApplicationEvent 로 변환한다.

 

@EventListener 는 어떻게 consuming 하는가

 

이제 여기서부터 @EventListener 는 어떻게 동작하는지에 대한 실마리가 나온다.

 

아까 봤던 AbstractApplicationContext 에서 조금 더 내려가다 보면 실제로 Event 를 multicast 하는 곳이 등장하게 된다.

 

ApplicationEventMulticaster 는 여러 ApplicationListener 객체를 관리하고 이벤트를 publish 할 수 있는 도록 하는 인터페이스인데,

 

실제로 호출되는 구현체는 바로 SimpleApplicationEventMulticaster 이다.

 

 

그리고 해당 구현체의 내부에서 @EventListener 어노테이션이 붙은 ApplicationListener 들을 모두 불러와서 적절한 Listener 에게 event 를 전달하게 되는 것이다.

 

 

References

댓글0