// 이벤트는 빈으로 등록되지 않는다. 원하는 데이터를 전달하는 역할이다.
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;
}
}
이벤트를 정의한다.
@Component
public class AppRunner implements ApplicationRunner {
// ApplicationContext가 Publisher를 상속하고 있으므로 이것을 써도 되지만 Publisher를 직접 써도 된다.
@Autowired
ApplicationEventPublisher applicationEventPublisher;
@Override
public void run(ApplicationArguments args) throws Exception {
// publishEvent로 이벤트를 발생시킨다.
applicationEventPublisher.publishEvent(new MyEvent(this, 100));
}
}
애플리케이션 컨텍스트는 publish 즉, 이벤트 발생 기능을 가지고 있다.
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
}
이벤트를 받아서 처리하는 일은 이벤트 핸들러가 담당한다.
이벤트 핸들러는 빈으로 등록해야 한다.
이벤트 받았다. 데이터는 100
순서를 보면
AppRunner의 publishEvent가 이벤트를 발생시킨다.
등록되어 있는 빈 중에서 MyEventHandler가 이벤트를 받는다.
MyEventHandler가 해당 이벤트를 출력한다.
개선된 버전
스프링 4.2 이후부터는 MyEvent가 ApplicationEvent를 상속하거나 MyEventHandler가 ApplicationListener를 상속하는 방법을 쓰지 않아도 된다.
package me.whiteship.eventpublisher;
public class MyEvent {
private int data;
// ApplicationEvent는 지웠지만 이벤트를 발생시킨 소스를 갖고 있고 싶다면 이렇게 한다.
private Object source;
public MyEvent(Object source, int data) {
this.source = source;
this.data = data;
}
public Object getSource() {
return source;
}
public int getData() {
return data;
}
}
@Component
public class MyEventHandler {
// 이벤트를 처리하는 메서드 위에 에너테이션을 추가해준다.
// 이제 메서드 이름을 마음대로 바꿔도 된다.
@EventListener
public void handle(MyEvent myEvent) {
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
}
이벤트 핸들러는 상속은 하지 않더라도 꼭 빈으로 등록되어야 한다.
그래야 누가 처리할지 알 수 있기 때문이다.
두 개 이상의 이벤트 처리
기본적으로는 순차적으로 처리가 된다.
순차적이란 말은 뭐가 먼저 실행될지는 모르지만 동시에 하는 게 아니라 하나씩 실행된다는 말이다.
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent myEvent) {
// 스레드 확인용
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
}
@Component
public class AnotherHandler {
@EventListener
public void handle(MyEvent myEvent) {
// 스레드 확인용
System.out.println(Thread.currentThread().toString());
System.out.println("Another " + myEvent.getData());
}
}
Thread[main,5,main]
Another 100
Thread[main,5,main]
이벤트 받았다. 데이터는 100
둘 다 main 스레드에서 실행되고 있으며 순서는 랜덤이다.
순서 지정
@Order를 사용한다.
@Component
public class MyEventHandler {
@EventListener
// 실행 순서 지정
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
}
@Component
public class AnotherHandler {
@EventListener
// 값을 더하면 더 나중에 실행된다.
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another " + myEvent.getData());
}
}
Thread[main,5,main]
이벤트 받았다. 데이터는 100
Thread[main,5,main]
Another 100
순서를 지정한대로 실행되었다.
비동기 실행
@Async로 비동기를 적용할 수 있다. 순서는 스레드 스케줄링에 따라 다르므로 보장되지 않는다.
@SpringBootApplication
// Async 설정
@EnableAsync
public class EventPublisherApplication {
public static void main(String[] args) {
SpringApplication.run(EventPublisherApplication.class, args);
}
}
@Component
public class AnotherHandler {
@EventListener
@Async
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another " + myEvent.getData());
}
}
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
}
Thread[task-2,5,main]
이벤트 받았다. 데이터는 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(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 받았다. 데이터는 " + myEvent.getData());
}
@EventListener
@Async
public void handle(ContextRefreshedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextRefreshedEvent");
}
@EventListener
@Async
public void handle(ContextClosedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextClosedEvent");
}
}
2020-03-17 17:16:22.706 INFO 33745 --- [ main] m.w.e.EventPublisherApplication : Starting EventPublisherApplication on macbook.local with PID 33745 (/Users/sojeong/dev/IntelliJ-workspace/spring-keesun-baik/spring-framework-core-07-event-publisher/target/classes started by sojeong in /Users/sojeong/dev/IntelliJ-workspace/spring-keesun-baik)
2020-03-17 17:16:22.709 INFO 33745 --- [ main] m.w.e.EventPublisherApplication : No active profile set, falling back to default profiles: default
2020-03-17 17:16:23.372 INFO 33745 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-03-17 17:16:23.379 INFO 33745 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-03-17 17:16:23.379 INFO 33745 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31]
2020-03-17 17:16:23.428 INFO 33745 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-03-17 17:16:23.428 INFO 33745 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 672 ms
2020-03-17 17:16:23.576 INFO 33745 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
Thread[task-1,5,main]
ContextRefreshedEvent
2020-03-17 17:16:23.750 INFO 33745 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-03-17 17:16:23.754 INFO 33745 --- [ main] m.w.e.EventPublisherApplication : Started EventPublisherApplication in 1.281 seconds (JVM running for 7.094)
Thread[task-3,5,main]
이벤트 받았다. 데이터는 100
Thread[task-2,5,main]
Another 100
//// 애플리케이션을 종료했을 경우 ////
Thread[task-4,5,main]
ContextClosedEvent
2020-03-17 17:17:43.798 INFO 33745 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)