- [Spring] ApplicationEventPublisher란? 목차
1.ApplicationEvnetPublisher 란?
->디자인 패턴 중 하나인 Observer Pattern과 유사한 기능을 합니다. 다시 말해 Spring Framework를 통해 손쉽게 Observer Pattern을 구현하는 것입니다.
2.언제 사용하나요?
(관련 글 - https://www.javacodegeeks.com/2012/08/observer-pattern-with-spring-events.html)
Observer Pattern을 사용할 때를 떠올리면 됩니다. 이벤트를 발생하는 Publisher와 이를 구독하는 Observer(or Subscriber)사이의 결합도를 낮추면서도 이벤트를 Observer에게 전달을 하고싶을 때 사용하게 됩니다.
3.구조
3.1ApplicationEventPublisher
구독 대상 클래스인 Observable은 ApplicationEventPublisher를 구현합니다. 이는 구독자들에게 publish() 메소드를 통해 event가 발생하면 Event를 넘겨주어 Observer가 특정 처리를 하도록 합니다. 하지만 우리는 Spring에서 제공해주는 ApplicationEventPublisher를 자동으로 주입받아 사용할 것입니다.
3.2 ApplicationEvent
말그대로 Event를 전달하기 위한 객체입니다. ApplicationEvent를 상속받아 사용자가 원하는 데이터를 입력받도록 구현하면 됩니다.
3.3 ApplicationListener<E extends ApplicationEvent>
Observable을 구독하는 Observer의 역할을 하게 될 Class입니다. 기본적으로 ApplicationListener을 구현하여, onApplicationEvent(E) 메소드를 구현하게 됩니다. 이 onApplicationEvent(E) 메소드는 Spring에서 제공하는 ApplicationEventPublisher에서 특정 이벤트를 publish 하는 경우 자동으로 실행되게 되어있습니다. 따라서 매개변수로 전달되는 event를 이용해 원하는 작업을 처리하도록 구현할 수 있습니다. 또한 제네릭으로 ApplicationEvent를 상속받은 클래스를 넣어주어 컴파일 타임에 형 안정성을 보장 받을 수 있도록 했습니다.
아래에서 예제를 통해 조금더 자세히 알아보도록 하겠습니다. 먼저 현재의 ApplicationEvnetPublisher의 모습을 보기전에 조금 더 구체적인 코드를 볼 수 있도록 Spring 4.2 이전에서 구현하는 방법을 살펴보도록 하겠습니다.
4. 예제
4.1 Spring 4.2 이전
Event
public class Event extends ApplicationEvent {
private String message;
public Event(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
먼저 event class입니다. 앞서 말씀드렸듯, ApplicationEvent를 상속받아 원하는 데이터를 매개변수로 전달받도록 할 수 있습니다. 간단한 String형의 message를 추가했습니다. Event의 경우 event를 발생시킬 때 생성할 것이므로 Bean에 등록이 되지 않아도 됩니다.
Observer(Handler)
@Component
public class Observer implements ApplicationListener<Event> {
@Override
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
observer class의 경우에는 ApplicationListener를 구현하여 매개변수로 전달되는 event의 message를 출력하도록 했습니다. Observer의 경우 IoC 컨테이너에 의해 사용됩니다. 따라서 Bean에 등록이 되어있어야 하므로 @Component 어노테이션을 추가해주었습니다.
실행
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher publisher;
@Override
public void run(ApplicationArguments args) throws Exception {
publisher.publishEvent(new Event(this, "hi"));
}
}
== 결과 ==
Event : hi
Observable은 어디갔냐구요? 바로 위의 ApplicationEventPublisher입니다.
ApplicationEventPublisher 역시 ApplicationContext가 구현하고 있으므로 IoC 컨테이너에 의해 제공되는 기능중 하나입니다.
4.2 Spring 4.2 이후
Event
public class Event {
private String message;
public Event(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
Spring 4.2 이후에는 Event가 ApplicationEvent를 상속받을 필요가 사라졌습니다. 아직 어떻게 가능한지는 모르겠지만 이 코드에는 Spring 코드가 전혀 추가되어있지 않은 것을 볼 수 있습니다. 이것이 바로 Spring이 추구하는 비침투성 이라고 합니다.
Observer(Handler)
@Component
public class Observer {
@EvnetListener
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
마찬가지로 Observer에서도 ApplicationListener<E>를 구현할 필요가 사라졌습니다. 하지만 handler 역할을 하는 Method위에 @EventListener어노테이션을 추가해주어야 합니다. 간단하죠? 간단한 김에 하나의 handler를 더 추가해보겠습니다.
MyObserver(Handler2)
@Component
public class MyObserver {
@EventListener
public void handler(Event event){
System.out.println(event.getMessage() + ", I'm galid.");
}
}
간단하게 MyObserver Class를 추가하고 handler 메소드위에 @EventListener어노테이션을 부여했습니다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher publisher;
@Override
public void run(ApplicationArguments args) throws Exception {
publisher.publishEvent(new Event(this, "hi"));
}
}
== 결과 ==
hi, I'm galid.
Event : hi
publisher는 그대로 ApplicationContext에서 제공하는 것을 사용하면 됩니다. 그런데 결과의 순서가 조금 이상하군요
5. EventLister 순서 지정하기
앞선 두개의 Handler를 사용하여 실행한 결과 2번째 생성한 handler가 먼저 실행된것을 볼 수 있습니다. 음 순서가 랜덤인건가... 그것은 자세히 모르겠지만 우리가 원하는 대로 event의 실행순서를 지정할 수 있습니다. 바로 @Order(Ordered.순서) 어노테이션을 이용하면 됩니다. 바로 예제를 보도록 하겠습니다.
@Order 이용하기
@Component
public class Observer {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
사용법은 간단합니다. @EventListener 어노테이션이 부여된 handler 메소드 위에 추가적으로 @Orderd어노테이션을 부여하면 됩니다. 괄호안에는 원하는 순서를 입력하면 되는데요, 간단히 HIGHEST_PRECEDENCE, LOWEST_PRECEDENCE를 제공하고 있습니다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationEventPublisher publisher;
@Override
public void run(ApplicationArguments args) throws Exception {
publisher.publishEvent(new Event(this, "hi"));
}
}
== 결과 ==
Event : hi
hi, I'm galid.
결과로 HIGEST_PRECEDENCE를 적용한 handler가 먼저 호출된것을 볼 수 있습니다.
세부적으로 순서 지정하기
단순히 최고 빠르게, 최고 느리게만 가능하냐구요? 아닙니다. 아래의 예제를 보겠습니다
MyObserver
@Component
public class MyObserver {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handler(Event event){
System.out.println(event.getMessage() + ", I'm galid.");
}
}
이번에는 MyObserver의 handler에도 HIGEST Order을 적용했습니다. 두 handler에 모두 HIGEST가 적용되었네요. 이상태에서 Observer가 2번째로 실행되도록 하겠습니다.
Observer
@Component
public class Observer {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
바로 Ordered.HIGHEST_PRECEDENCE 에 + 1을 해주면 됩니다.
6. Spring이 제공하는 Event
1. ContextRefreshedEvent
- ApplicationContext를 초기화 했거나 리프레시 했을 때 발생
2. ContextStartedEvent
- ApplicationContext를 start()하여 라이프사이클 Bean들이 시작 신호를 받은 시점에 발생
3. ContextStoppedEvent
- ApplicationContext를 stop()하여 라이프사이클 Bean들이 정지 신호를 받은 시점에 발생.
4. ContextClosedEvent
- ApplicationContext를 close()하여 Singleton Bean들이 소멸되는 시점에 발생
5. RequestHandledEvent
- HTTP 요청을 처리했을 때 발생.
@Component
public class Observer {
@EventListener
public void handle(ContextRefreshedEvent event){
System.out.println("Refresh");
}
@EventListener
public void handle(ContextStartedEvent event){
System.out.println("Started");
}
@EventListener
public void handle(ContextStoppedEvent event){
System.out.println("Stopped");
}
@EventListener
public void handle(ContextClosedEvent event){
System.out.println("Closed");
}
}
각각의 Evnet들은 적절한 매개변수를 가지는 메소드들을 생성한 후 @EventListener어노테이션을 부여하면 상황에 맞게 호출됩니다.
'Spring' 카테고리의 다른 글
[Spring] DataBinding추상화,Converter,Formatter란? (0) | 2020.01.14 |
---|---|
[Spring] Validation이란? (0) | 2020.01.13 |
[Spring] Environment 프로파일이란? (0) | 2020.01.13 |
[Spring] Bean의 Scope (0) | 2020.01.13 |
[Spring] @Component Scan 컴포넌트 스캔? (0) | 2020.01.13 |