[스프링 IoC 컨테이너와 빈]_09 ApplicationEventPublisher
ApplicationEventPublisher
이벤트 프로그래밍에 필요한 인터페이스 제공. 옵저버 패턴 구현체
ApplicationContext extends ApplicationEventPublisher
● publishEvent(ApplicationEvent event)
이벤트 만들기
● ApplicationEvent 상속
● 스프링 4.2 부터는 이 클래스를 상속받지 않아도 이벤트로 사용할 수 있다.
이벤트 발생 시키는 방법
● ApplicationEventPublisher.publishEvent();
이벤트 처리하는 방법
● ApplicationListener<이벤트> 구현한 클래스 만들어서 빈으로 등록하기.
● 스프링 4.2 부터는 [@EventListener](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/event/EventListener.html)를 사용해서 빈의 메소드에 사용할 수 있다.
● 기본적으로는 synchronized.
● 순서를 정하고 싶다면 @Order와 함께 사용.
● 비동기적으로 실행하고 싶다면 @Async와 함께 사용.
스프링이 제공하는 기본 이벤트
● ContextRefreshedEvent: ApplicationContext를 초기화 했더나 리프래시 했을 때 발생.
● ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈들이 시작
신호를 받은 시점에 발생.
● ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈들이 정지
신호를 받은 시점에 발생.
● ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에 발생.
● RequestHandledEvent: HTTP 요청을 처리했을 때 발생.
스프링부트 4.2 이전의 이벤트 처리
1. 이벤트 생성
이 이벤트는 빈으로 등록되는 것이 아니다.
스프링부트 4.2 이전 버전에서는 항상 ApplicationEvent
를 상속받았어야 했다.
다음 코드는 이벤트를 발생시킨 source만 전달하고 있다.
public class MyEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public MyEvent(Object source) {
super(source);
}
}
원하는 데이터를 담아서 전송하는 기능의 이벤트를 생성
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;
}
}
2. 이벤트 발생
- ApplicationEventPublisher.publishEvent();
이벤트를 Publish하는 기능을 ApplicationEventPublisher
가 가지고 있다.
ApplicationContext가 ApplicationEventPublisher
를 상속받았기 때문에 ApplicationContext로 이벤트를 발생시킬 수 있다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher publisherEvent;
// ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
publisherEvent.publishEvent(new MyEvent(this, 100)); //이벤트 발생
// applicationContext.publishEvent(new MyEvent(this, 100));
}
}
3. 이벤트 핸들러
발생한 이벤트를 받아서 처리
이벤트 핸들러는 빈으로 등록이 되어야한다.
스프링부트 4.2 이전에는 ApplicationListener<>
라는 인터페이스를 구현했어야 했다.
( 4.2 이후 부터는 ApplicationListener를 구현해야하는 제약사항이 사라졌다. )
이벤트가 발생하면 등록되어있는 빈들 중 MyEventHandler가 받아서 처리한다.
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
//전달받은 event로 원하는 작업을 하면 된다.
System.out.println("이벤트 전달 받음!!!! 데이터는 : " + event.getData());
}
}
스프링부트 4.2 이후의 이벤트 처리 (추천)
ApplicationEvent
을 구현해야하는 제약사항이 사라짐"비침투성"
을 지키며 코드를 작성할 수 있게 됨
1. 이벤트 생성
ApplicationEvent를 구현하지 않고 이벤트를 생성하도록 코드를 수정해보자
(※ 이벤트는 빈이 아님!!)
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 int getData() {
return data;
}
}
2. 이벤트 핸들러
@EventListener
어노테이션
ApplicationListener<>
라는 인터페이스를 구현하지 않아도 되는 대신, 스프링이 핸들러를 알아야하므로 빈으로 등록해줘야 한다.
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
System.out.println("이벤트 전달 받음!!!! 데이터는 : " + event.getData());
}
}
⇒ 실행하면 정상적으로 동작한다.
⇒ 이게 바로 스프링이 추구하는 철학이다. “비침투성”
내 코드에 스프링 프레임워크 코드가 들어가 있지 않다. 이게 가장 깔끔한 포조이다. ⇒ “포조기반 프로그래밍”
테스트할 때 더 편하고, 유지보수하기 좋아진다.
다수개 핸들러가 존재할 때 이벤트 처리
이벤트 핸들러가 여러가지 있을 때, 두 핸들러 모두 실행된다. 기본적으로 순차적으로 실행된다. 뭐가 먼저 실행될지는 모르지만, 한 이벤트가 실행된 후 다음 이벤트가 실행된다. 즉, 두 핸들러를 다른 스레드로 동시에 실행시켜주지 않는다는 의미
※ [실습]
또 다른 핸들러를 생성
@Component
public class AnotherHandler {
@EventListener
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString()); //현재 스레드정보 출력
System.out.println("Another Handler !!! 데이터는 : " + myEvent.getData() );
}
}
⇒ main 스레드로 순차적으로 두 핸들러가 모두 실행된다.
순서를 정해서 이벤트 처리
-
EventHandler에
@Order
어노테이션으로 우선순위를 부여하면 된다. @Order(Ordered.HIGHEST_PRECEDENCE)
@Order(Ordered.HIGHEST_PRECEDENCE + n)
: 우선순위에 값을 더해주면 순위가 밀린다.
MyEventHandler
@Component
public class MyEventHandler {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE) //<- 더 높은 우선순위
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 전달 받음!!!! 데이터는 : " + event.getData());
}
}
AnotherHandler
@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 Handler !!! 데이터는 : " + myEvent.getData() );
}
}
⇒ 실행결과
비동기적으로 이벤트 실행
각각의 스레드 풀에서 돌고, 어떤 스레드가 먼저 실행될지는 스레드 스케쥴링에 따라서 결정되기 때문에 @Order
로 순서를 지정하는 것은 의미가 없다.
-
핸들러에
@Async
어노테이션을 붙인다.@Async
어노테이션만 붙인다고 비동기적으로 실행되진 않는다. -
main 클래스에
@EnableAsync
를 붙인다.(스레드 관련 설정은 더 있지만 현재는 간략하게 디폴트 스레드 풀에서 동작하도록 한다.)
@Component
public class AnotherHandler {
@EventListener
@Async //<-- *
public void handle(MyEvent myEvent) {
System.out.println(Thread.currentThread().toString());
System.out.println("Another Handler !!! 데이터는 : " + myEvent.getData());
}
}
@SpringBootApplication
@EnableAsync //<-- *
public class Demospring51Application {
public static void main(String[] args) {
SpringApplication.run(Demospring51Application.class, args);
}
}
⇒ 각각의 스레드에서 동작한다.
스프링이 제공하는 기본 이벤트
스프링이 기본으로 제공해주는 ApplicationContext관련 이벤트가 있다.
- ContextRefreshedEvent: ApplicationContext를 초기화 했더나 리프래시 했을 때 발생.
- ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈들이 시작
스프링이 기본으로 제공해주는 ApplicationContext관련 이벤트를 처리해주는 핸들러 추가
@Component
public class MyEventHandler {
@EventListener
@Async
public void handle(MyEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("이벤트 전달 받음!!!! 데이터는 : " + event.getData());
}
@EventListener
@Async
public void handle(ContextRefreshedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextRefreshedEvent");
ApplicationContext applicationContext = event.getApplicationContext(); //직접 ApplicationContext를 꺼내서 사용할 수도 있다.
System.out.println("==>>> " + applicationContext.getClass());
}
@EventListener
@Async
public void handle(ContextClosedEvent event) {
System.out.println(Thread.currentThread().toString());
System.out.println("ContextClosedEvent");
}
}
⇒ 스프링부트가 이러한 여러 이벤트들을 확장해서 더 많이 제공해준다. (→ 자세한 내용은 스프링부트 강좌에서)