// 이벤트는 빈으로 등록되지 않는다. 원하는 데이터를 전달하는 역할이다.publicclassMyEventextendsApplicationEvent {privateint data;publicMyEvent(Object source) { super(source); }publicMyEvent(Object source,int data) { super(source);this.data= data; }publicintgetData() {return data; }}
이벤트를 정의한다.
@ComponentpublicclassAppRunnerimplementsApplicationRunner {// ApplicationContext가 Publisher를 상속하고 있으므로 이것을 써도 되지만 Publisher를 직접 써도 된다. @AutowiredApplicationEventPublisher applicationEventPublisher; @Overridepublicvoidrun(ApplicationArguments args) throwsException {// publishEvent로 이벤트를 발생시킨다.applicationEventPublisher.publishEvent(newMyEvent(this,100)); }}
애플리케이션 컨텍스트는 publish 즉, 이벤트 발생 기능을 가지고 있다.
@ComponentpublicclassMyEventHandlerimplementsApplicationListener<MyEvent> { @OverridepublicvoidonApplicationEvent(MyEvent myEvent) {System.out.println("이벤트 받았다. 데이터는 "+myEvent.getData()); }}
이벤트를 받아서 처리하는 일은 이벤트 핸들러가 담당한다.
이벤트 핸들러는 빈으로 등록해야 한다.
이벤트 받았다. 데이터는 100
순서를 보면
AppRunner의 publishEvent가 이벤트를 발생시킨다.
등록되어 있는 빈 중에서 MyEventHandler가 이벤트를 받는다.
MyEventHandler가 해당 이벤트를 출력한다.
개선된 버전
스프링 4.2 이후부터는 MyEvent가 ApplicationEvent를 상속하거나 MyEventHandler가 ApplicationListener를 상속하는 방법을 쓰지 않아도 된다.
packageme.whiteship.eventpublisher;publicclassMyEvent {privateint data;// ApplicationEvent는 지웠지만 이벤트를 발생시킨 소스를 갖고 있고 싶다면 이렇게 한다.privateObject source;publicMyEvent(Object source,int data) {this.source= source;this.data= data; }publicObjectgetSource() {return source; }publicintgetData() {return data; }}
@ComponentpublicclassMyEventHandler {// 이벤트를 처리하는 메서드 위에 에너테이션을 추가해준다.// 이제 메서드 이름을 마음대로 바꿔도 된다. @EventListenerpublicvoidhandle(MyEvent myEvent) {System.out.println("이벤트 받았다. 데이터는 "+myEvent.getData()); }}
이벤트 핸들러는 상속은 하지 않더라도 꼭 빈으로 등록되어야 한다.
그래야 누가 처리할지 알 수 있기 때문이다.
두 개 이상의 이벤트 처리
기본적으로는 순차적으로 처리가 된다.
순차적이란 말은 뭐가 먼저 실행될지는 모르지만 동시에 하는 게 아니라 하나씩 실행된다는 말이다.
Thread[main,5,main]
Another 100
Thread[main,5,main]
이벤트 받았다. 데이터는 100
둘 다 main 스레드에서 실행되고 있으며 순서는 랜덤이다.
순서 지정
@Order를 사용한다.
@ComponentpublicclassMyEventHandler { @EventListener// 실행 순서 지정 @Order(Ordered.HIGHEST_PRECEDENCE)publicvoidhandle(MyEvent myEvent) {System.out.println(Thread.currentThread().toString());System.out.println("이벤트 받았다. 데이터는 "+myEvent.getData()); }}
@ComponentpublicclassAnotherHandler { @EventListener// 값을 더하면 더 나중에 실행된다. @Order(Ordered.HIGHEST_PRECEDENCE+2)publicvoidhandle(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로 비동기를 적용할 수 있다. 순서는 스레드 스케줄링에 따라 다르므로 보장되지 않는다.
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)