ApplicationEventPublisher
ApplicationContext는 이벤트 프로그래밍에 필요한 인터페이스 제공한다. (옵저버 패턴 구현체)
Event를 만드는 방법
Spring 4.2 미만의 버전에서는 ApplicationEvent를 상속받아야 했지만 이상의 버전에서는 상속받지 않아도 사용할 수 있다.
4.2 버전 미만
MyEvent.java
public class MyEvent extends ApplicationEvent {
private int data;
public MyEvent(Object source) {
super(source);
}
public MyEvent(Object source, int data) {
super(source);
this.data = data;
}
public int getData() {
return data;
}
}
4.2 버전 이상
MyEvent.java
public class MyEvent {
private int data;
private Object source;
public MyEvent(int data, Object source) {
this.data = data;
this.source = source;
}
public Object getSource() {
return source;
}
public int getData() {
return data;
}
}
- 위와 같은 코딩 방법이 Spring이 추구하는 철학이다. (비 침투성, non-invasive)
- framework 코드가 나의 코드에 노출되지 않는 것.
- POJO 기반의 프로그래밍 (테스트와 유지보수가 쉬워진다.)
- 가장 깔끔한 POJO
Event 처리 방법(EventHandler)
Spring 4.2 미만의 버전에서는 ApplicationListener 상속받아야 했지만 이상의 버전에서는 상속받지 않아도 사용할 수 있다.
4.2 버전 미만
MyEventHandler.java
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
}
4.2 버전 이상
- @EventListener를 사용하여 리스너를 만들 수 있다.
MyEventHandler.java
@Component
public class MyEventHandler {
@EventListener
public void myHandler(MyEvent myEvent) {
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
}
한 Event에 두개의 Listener가 존재한다면 ?
테스트를 위하여 코드를 아래와 같이 수정한다.
MyEventHandler.java
@Component
public class MyEventHandler {
@EventListener
public void myHandler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
}
AnotherHandler.java
@Component
public class AnotherHandler {
@EventListener
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("anotherHandler 데이터는 = " + myEvent.getData());
}
}
실행한 결과는 아래와 같다.
Thread[main,5,main]
anotherHandler 데이터는 = 100
Thread[main,5,main]
이벤트를 받았습니다. 데이터는 = 100
위와 같이 메인스레드에서 순차적으로 실행된다!
Listener의 순서를 정해주어 실행하는 방법(동기적)
- 아래의 코드와 같이 @Order Annotation을 사용하여 순서를 정해줄 수 있다.
MyEventHandler.java
@Component
public class MyEventHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public void myHandler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
}
AnotherHandler.java
@Component
public class AnotherHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("anotherHandler 데이터는 = " + myEvent.getData());
}
}
결과는 다음과 같이 MyEventHandler가 먼저 동작한다.
Thread[main,5,main]
이벤트를 받았습니다. 데이터는 = 100
Thread[main,5,main]
anotherHandler 데이터는 = 100
Ordered
- Ordered는 아래와 같이 선언되어 있다.
public interface Ordered {
int HIGHEST_PRECEDENCE = -2147483648;
int LOWEST_PRECEDENCE = 2147483647;
int getOrder();
}
작은 수 일수록 우선순위가 높은 것을 알 수 있다.
비 동기적 Listener 사용 방법
- 아래의 코드와 같이 @Async와 @Enabled Annotation을 사용하여 순서를 정해줄 수 있다.
MyEventHandler.java
@Component
public class MyEventHandler {
@EventListener
@Async
public void myHandler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
}
AnotherHandler.java
@Component
public class AnotherHandler {
@EventListener
@Async
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("anotherHandler 데이터는 = " + myEvent.getData());
}
}
Listener에 @Async Annotation만을 사옹한다고 비동기적으로 동작하지 않는다.
Application.java에서 @EnableAsync Annotation을 추가해야 비동기적으로 동작한다.
EventPublisherApplication.java
@SpringBootApplication
@EnableAsync
public class EventpublisherApplication {
public static void main(String[] args) {
SpringApplication.run(EventpublisherApplication.class, args);
}
}
결과는 아래와 같이 비동기적으로 동작한다.
Thread[task-2,5,main]
Thread[task-1,5,main]
이벤트를 받았습니다. 데이터는 = 100
anotherHandler 데이터는 = 100
원래는 ThreadPool 관련 설정을 더 진행해야 한다. 나중에 Async 관련 포스트에 추가할 예정이다.
ApplicationContext 관련 Event를 처리하는 Handler 추가해보기
MyEventHandler.java
@Component
public class MyEventHandler {
@EventListener
@Async
public void myHandler(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트를 받았습니다. 데이터는 = " + myEvent.getData());
}
@EventListener
@Async
public void handle(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEvent");
}
@EventListener
@Async
public void handle(ContextClosedEvent event) {
System.out.println("ContextClosedEvent");
}
}
위와 같이 ContextRefreshedEvent와 ContextClosedEvent 이벤트 리스너를 작성하고 Application을 실행하고 종료하면 해당 리스너가 동작하는 것을 알 수 있다.
ContextRefreshedEvent
2019-05-23 16:27:56.287 INFO 14047 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-05-23 16:27:56.291 INFO 14047 --- [ main] c.s.e.EventpublisherApplication : Started EventpublisherApplication in 3.267 seconds (JVM running for 4.636)
Thread[task-3,5,main]
이벤트를 받았습니다. 데이터는 = 100
Thread[task-2,5,main]
anotherHandler 데이터는 = 100
ContextClosedEvent
2019-05-23 16:28:01.565 INFO 14047 --- [ Thread-10] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
Spring boot 가 이와 같은 Event들을 더 많이 확장하여 더 다양한 Event들을 제공해 준다.
스프링이 제공하는 기본 이벤트
- ContextRefreshedEvent
- ApplicationContext를 초기화 했거나 refresh 했을 때 발생.
- ContextStartedEvent
- ApplicationContext를 start()하여 라이프사이클 Bean들이 시작 신호를 받은 시점에 발생.
- ContextStoppedEvent
- ApplicationContext를 stop()하여 라이프사이클 Bean들이 정지 신호를 받은 시점에 발생.
- ContextClosedEvent
- ApplicationContext를 close()하여 싱글톤 Bean이 소멸되는 시점에 발생.
- RequestHandledEvent
- HTTP 요청을 처리했을 때 발생.
Project Repository
- https://github.com/Seongmun-Hong/SpringStudy
Reference
- https://www.inflearn.com/course/spring-framework_core