1. ApplicationEvnetPublisher가 뭐죠?
IoC 컨테이너의 기능 중 하나인 ApplicationEventPublisher
에 대해서 알아보겠습니다. 이 기능은 마치 디자인 패턴 중 하나인 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.1 ApplicationEventPublisher
구독 대상 클래스인 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
x
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)
xxxxxxxxxx
public class Observer implements ApplicationListener<Event> {
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
observer class의 경우에는 ApplicationListener
를 구현하여 매개변수로 전달되는 event의 message를 출력하도록 했습니다. Observer의 경우 IoC 컨테이너에 의해 사용됩니다. 따라서 Bean에 등록이 되어있어야 하므로 @Component 어노테이션을 추가해주었습니다.
실행
xxxxxxxxxx
public class AppRunner implements ApplicationRunner {
ApplicationEventPublisher publisher;
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
xxxxxxxxxx
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)
xxxxxxxxxx
public class Observer {
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
마찬가지로 Observer에서도 ApplicationListener<E>
를 구현할 필요가 사라졌습니다. 하지만 handler 역할을 하는 Method위에 @EventListener
어노테이션을 추가해주어야 합니다. 간단하죠? 간단한 김에 하나의 handler를 더 추가해보겠습니다.
MyObserver(Handler2)
public class MyObserver {
public void handler(Event event){
System.out.println(event.getMessage() + ", I'm galid.");
}
}
간단하게 MyObserver Class를 추가하고 handler 메소드위에 @EventListener
어노테이션을 부여했습니다.
xxxxxxxxxx
public class AppRunner implements ApplicationRunner {
ApplicationEventPublisher publisher;
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 이용하기
xxxxxxxxxx
public class Observer {
Ordered.HIGHEST_PRECEDENCE) (
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
사용법은 간단합니다. @EventListener
어노테이션이 부여된 handler 메소드 위에 추가적으로 @Order
d어노테이션을 부여하면 됩니다. 괄호안에는 원하는 순서를 입력하면 되는데요, 간단히 HIGHEST_PRECEDENCE, LOWEST_PRECEDENCE
를 제공하고 있습니다.
xxxxxxxxxx
public class AppRunner implements ApplicationRunner {
ApplicationEventPublisher publisher;
public void run(ApplicationArguments args) throws Exception {
publisher.publishEvent(new Event(this, "hi"));
}
}
== 결과 ==
Event : hi
hi, I'm galid.
결과로 HIGEST_PRECEDENCE
를 적용한 handler가 먼저 호출된것을 볼 수 있습니다.
세부적으로 순서 지정하기
단순히 최고 빠르게, 최고 느리게만 가능하냐구요? 아닙니다. 아래의 예제를 보겠습니다.
MyObserver
public class MyObserver {
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
public class Observer {
Ordered.HIGHEST_PRECEDENCE + 1) (
public void onApplicationEvent(Event event) {
System.out.println("Event : " + event.getMessage());
}
}
바로 Ordered.HIGHEST_PRECEDENCE
에 + 1
을 해주면 됩니다.
실행
...
hi, I'm galid.
Event : hi
MyObserver의 Handler가 먼저 실행된것을 볼 수 있습니다.
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 요청을 처리했을 때 발생.
xxxxxxxxxx
public class Observer {
public void handle(ContextRefreshedEvent event){
System.out.println("Refresh");
}
public void handle(ContextStartedEvent event){
System.out.println("Started");
}
public void handle(ContextStoppedEvent event){
System.out.println("Stopped");
}
public void handle(ContextClosedEvent event){
System.out.println("Closed");
}
}
각각의 Evnet들은 적절한 매개변수를 가지는 메소드들을 생성한 후 @EventListener
어노테이션을 부여하면 상황에 맞게 호출됩니다.
'FrameWork > Spring' 카테고리의 다른 글
Spring - IoC 컨테이너의 기능 - 6 (DataBinding 추상화, Converter, Formatter 란?) (0) | 2019.04.16 |
---|---|
Spring - IoC 컨테이너의 기능 - 5 (객체의 값 검증 Validation, Errors) (2) | 2019.04.14 |
Spring - IoC 컨테이너의 기능 - 3 (MessageSource 란?) (0) | 2019.04.13 |
Spring - IoC 컨테이너의 기능 - 2 (Environment 란? , 분석 (Property, ConfigurableEnvironment, MutablePropertyResource)) (0) | 2019.04.12 |
Spring - IoC 컨테이너의 기능 - 1 (Bean의 Scope) (0) | 2019.04.11 |