[Spring] ApplicationEventPublisher를 통한 스프링 이벤트 처리(ApplicationEventPublisher, Spring Event Processing)
| 스프링 ApplicationEventPublisher
스프링 ApplicationEventPublisher는 스프링에서 이벤트 프로그래밍에 필요한 인터페이스를 제공한다. ApplicationContext 인터페이스에 이미 상속되어있어서 ApplicationContext의 구현체에서도 접근이 가능하다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext applicationContext; // ApplicationEventPublisher를 사용해도 된다.
@Override
public void run(ApplicationArguments args) throws Exception {
applicationContext.publishEvent(new MyEvent(this, 100));
((ConfigurableApplicationContext)applicationContext).close();
}
}
| 이벤트 등록 및 수신방법
publishEvent 메서드는 ApplicationEventPublisher가 제공하는 메서드이며 이 메서드를 통해 스프링 기반 어플리케이션에 이벤트를 발생시킬 수 있다.
public class MyEvent {
private int data;
private Object source;
public MyEvent(Object source, int data){
this.source = source;
this.data = data;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
이벤트는 POJO 스타일로 만들 수 있다. 원래는 ApplicationEvent를 상속받아 만들어야 했지만 Spring 4.2부터는 클래스를 상속받지 않아도 이벤트로 사용할 수 있다.
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event){
System.out.println("Event received " + event.getData());
}
}
위 MyEvent를 수신하는 방법은 스프링 빈으로 등록된 클래스의 메소드에 @EventListener를 등록, 위에 작성했던 MyEvent를 인자로 받는 메서드를 작성하면 된다.
@SpringBootApplication
public class SpringTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTutorialApplication.class, args);
}
}
스프링부트 어플리케이션을 실행시키면 다음과 아래와 같은 출력이 뜬다.
Thread[main,5,main]
Event received 100
| 이벤트 수신 순서 정하는 @Order
여러 핸들러를 추가하고 난 후 그 Handler의 이벤트를 수신할 시 순서를 정하고 싶으면 @Order 어노테이션을 붙여야 한다. @Order 어노테이션 안의 HIGHEST_PRECEDENCE는 Integer.MIN_VALUE로 값이 낮을 수록 우선순위를 먼저 점한다는 의미를 가지고 있다. 따라서 낮은 값에서 높은 값 순서로 이벤트의 우선순위를 점할 수 있다.
@Component
public class AnotherHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public void handle(MyEvent myEvent){
System.out.println(Thread.currentThread().toString());
System.out.println("Another " + myEvent.getData());
}
}
@Component
public class MyEventHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public void handle(MyEvent event){
System.out.println(Thread.currentThread().toString());
System.out.println("Event received " + event.getData());
}
}
Thread[main,5,main]
Another 100
Thread[main,5,main]
Event received 100
| 비동기적인 이벤트 수신을 위한 @Async
비동기적으로 이벤트를 수신하기 위해서는 스프링부트 어플리케이션이 실행되는 main 진입점에 @EnableAsync 어노테이션을 붙여야 한다.
@SpringBootApplication
@EnableAsync
public class SpringTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTutorialApplication.class, args);
}
}
다음 @EventListener 어노테이션이 붙은 매서드에 @Async를 붙여 이 메서드가 비동기적으로 메세지를 수신할 것이라는 것을 명시해야 한다. 이때 비동기적으로 메세지를 수신하므로 @Order 어노테이션은 의미가 없어진다.
@Component
public class AnotherHandler {
@EventListener
@Async
public void handle(MyEvent myEvent){
System.out.println(Thread.currentThread().toString());
System.out.println("Another " + myEvent.getData());
}
}
Thread[main,5,main]
Event received 100
Thread[task-1,5,main]
Another 100
| 스프링이 제공하는 기본 이벤트
- ContextRefreshedEvent : ApplicationContext를 초기화 했거나 리프래시 했을 때 발생.
- ContextStartedEvent : ApplicationContext를 start()하여 라이프사이클 빈들이 시작 신호를 받은 시점에 발생
- ContextStoppedEvent : ApplicationContext를 stop()하여 라이프사이클 빈들이 정지 신호를 받은 시점에 발생
- ContextClosedEvent : ApplicationContext를 close() 하여 싱글턴 빈들이 소멸되는 시점에 발생.
- RequestHandledEvent : HTTP 요청을 처리했을 때 발생.
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(ContextRefreshedEvent event){
System.out.println(Thread.currentThread().toString());
System.out.println("ContextRefreshEvent");
}
@EventListener
@Async
public void handle(ContextStartedEvent event){
System.out.println(Thread.currentThread().toString());
System.out.println("ContextStartedEvent");
}
@EventListener
@Async
public void handle(ContextStoppedEvent event){
System.out.println(Thread.currentThread().toString());
System.out.println("ContextStoppedEvent");
}
@EventListener
@Async
public void handle(ContextClosedEvent event){
System.out.println(Thread.currentThread().toString());
System.out.println("ContextClosedEvent");
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext applicationContext; // ApplicationEventPublisher를 사용해도 된다.
@Override
public void run(ApplicationArguments args) throws Exception {
applicationContext.publishEvent(new MyEvent(this, 100));
((ConfigurableApplicationContext)applicationContext).start();
((ConfigurableApplicationContext)applicationContext).stop();
((ConfigurableApplicationContext)applicationContext).close();
}
}
2018-12-13 12:06:14.841 INFO 14100 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1019 ms
2018-12-13 12:06:15.077 INFO 14100 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
Thread[task-1,5,main]
ContextRefreshEvent
2018-12-13 12:06:15.234 INFO 14100 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-13 12:06:15.237 INFO 14100 --- [ main] c.s.s.SpringTutorialApplication : Started SpringTutorialApplication in 1.728 seconds (JVM running for 2.35)
Thread[task-2,5,main]
ContextStartedEvent
Thread[task-3,5,main]
ContextStoppedEvent
Thread[task-4,5,main]
ContextClosedEvent
2018-12-13 12:06:15.243 INFO 14100 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'