스프링은 옵저버 패턴의 구현체로써, 이벤트 프로그래밍에 필요한 인터페이스 ApplicationEventPublisher를 제공한다.
옵저버 패턴 : 어떠한 상태를 가지는 객체와 이 객체의 상태를 관찰하는 객체를 가진 패턴을 옵저버 패턴이라고 한다.
ApplicationContext는 ApplicationEventPublisher를 상속 받고, publishEvent를 통해 이벤트를 발생시킬 수 있다.
스프링 4.2 이전에는 ApplicationEvent를 상속받아 이벤트를 만들수 있었다.
이벤트를 발생시키기 위해선 ApplicationContext나 ApplicationEventPublisher의 publishEvent를 통해 이벤트를 발생시킨다.
발생시킨 이벤트를 처리하기 위해서는 ApplicationListener<이벤트>를 구현한 핸들러 클래스를 만들어 빈으로 등록해야한다.
어플리케이션을 실행시키면, publishEvent(..)가 실행되면서 MyEvent를 발생시키고,
등록되어있는 빈 중에서 MyEventHandler를 받아 onApplicationEvent가 실행되면서 이벤트에 있는 데이터를 가져온다.
스프링의 목적 중 하나 : 비침투성 ( 기술에 적용 사실이 코드에 직접 반영되지 않음)
스프링에서는 MyEventHandler 클래스 처럼 또 다른 이벤트 핸들러를 등록해서 사용할 수 있다.
MyEventHandler 외에 AnotherHandler를 만들어 실행 시켜보면,
어떤 Handler가 먼저 실행되는 건지는 알 수 없으나 순차적으로 이벤트를 처리하는 것을 알 수 있다.
또한, 두 Handler가 서로 다른 스레드에서 이벤드를 처리하는 것이 아니라 두개 다 Main Thread에서 다뤄진다.
이때, 순서를 임의로 정해주고 싶다면 @Order를 사용하면 된다.
위에서는 another 핸들러가 먼저 발생되었는데
이번에는 @Order 어노테이션으로 우선 순위를 설정하여 main이 먼저 실행된 것을 알 수 있다.
이벤트 핸들러를 비동기 적으로 사용하고 싶다면 이벤트 핸들러에 @Async 어노테이션을 선언하여 사용할 수 있다.
이런 경우 서로 다른 스레드풀에 따로 돌기 때문에 @Order 어노테이션이 더이상 의미가 없어지게 된다.
실제로는 이벤트 핸들러에 @Async 어노테이션만 추가해준다고해서 비동기적으로 동작하는 것은 아니다. 아래와 같이 main 함수를 가지고 있는 클래스에 @EnableAsnc 어노테이션을 추가해야 한다.
비동기적으로 실행되면서 두개의 다른 스레드에서 동작하는 것을 확인 할 수 있다.
스프링에서 기본적으로 제공하는 ApplicationContext관련 이벤트가 있다.
ContextRefreshedEvent : ApplicationContext를 초기화 했거나 리프레시 했을 때 발생.
ContextStartedEvent : ApplicationContext를 start()하여 라이프사이클 빈들이 시작 신호를 받은 시점에 발생.
ContextStoppedEvent : ApplicationContext를 stop()하여 라이프사이클 빈들이 정지 신호를 받은 시점에 발생.
ContextClosedEvent : ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에 발생.
RequestHandledEvent : HTTP 요청을 처리했을 때 발생.
@Component
public class MyEventHandler {
@EventListener
@Async
public void occurEvent(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("My Event, data = "+ myEvent.getData());
}
@EventListener
@Async
public void occurEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextRefreshedEvent");
}
@EventListener
@Async
public void occurEvent(ContextClosedEvent contextClosedEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextClosedEvent");
}
}
위와 같이 수정하면 ContextRefreshedEvent 발생하고 ApplicationContext가 종료될때 ContextClosedEvent 이벤트가 발생하는 것을 확인할 수 있다.