-
[08] ApplicationContext - ApplicationEventPublisherSpring/Spring 핵심 기술 2020. 4. 19. 09:25반응형
Spring은 event programming을 위한 ApplicationEventPublisher라는 interface(옵저버 패턴 구현체)를 제공하고 있으며 ApplicationContext는 ApplicationEventPublisher를 상속받고 있습니다. 따라서 IoC Container에서 event programming을 위해 필요한 기능을 사용할 수 있습니다.
[event의 정의]
event를 정의하는 class를 먼저 만들어줍니다. 여기서는 MyEvent.java로 만들겠습니다. Spring 4.2 이전 버전에서는 ApplicationEvent라는 class를 상속받아 만듭니다.
public class MyEvent extends ApplicationEvent { private int myData; //사용자 데이터 public MyEvent(Object source) { super(source); } public MyEvent(Object source, int data) { super(source); this.myData = data; //사용자 데이터를 담아서 전송 } }
정의된 event는 bean이 아닙니다. event는 사용자의 데이터와 source를 담아서 전달하는 객체입니다.
Spring 4.2 부터는 더 이상 ApplicationEvent class를 상속받을 필요가 없어 다음과 같은 code가 됩니다.
public class MyEvent { private int myData; private Object source; public MyEvent(Object source, int data) { this.source = source this.myData = data; } public int getMyData() { return myData; } public Object getSource() { return source; } }
이와 같이 비침투성 즉, 개발자의 코드에 Spring code가 들어가지 않도록(package가 import되지 않도록)하여 코드를 POJO형태로 만들어 유지보수와 테스트가 용이하도록 하는 것이 Spring의 철학입니다.
[event 발생시키기]
정의한 event를 publishing 즉 발생시키는 기능을 ApplicationContext에서 가지고 있습니다. runner class(AppRunner.java)에서 event publishing을 하는 code는 다음과 같습니다.
@Component public class AppRunner implements ApplicationRunner { @Autowired ApplicationEventPublisher applicationEventPublisher; //1 @Override public void run(ApplicationArguments args) throws Exception { applicationEventPublisher.publishEvent(new MyEvent(this, 100)); //2 } }
- ApplicationEventPublisher bean을 주입받습니다.
- 현재 객체(AppRunner)와 사용자 데이터(100)을 전달하여 정의해 놓은 event class 객체를 생성합니다
[event를 처리]
event handler를 만들어 event를 처리합니다. event handler는 bean으로 등록됩니다. 여기서는 MyEventHandler.java class를 만들어보도록 합시다. Spring 4.2 이전에는 ApplictaionListener<Event type>의 generic type을 구현하여 만들었습니다.
@Component //1 public class MyEventHandler implements ApplicationListener<MyEvent> { @Override public void onApplicationEvent(MyEvent event) { //2 System.out.println("Event was published. Data is " + event.getMyData()); } }
- event handler는 bean으로 등록합니다.
- ApplicationListener<E>의 onApplicationEvent() method를 재정의 하여 event 처리 구문을 작성합니다.
Spring 4.2 부터는 ApplicationListener<M> 타입의 interface를 더 이상 구현할 필요가 없어 code는 다음과 같이 변경할 수 있습니다.
@Component public class MyEventHandler { @EventListener //1 public void handler(MyEvent event) { //2 System.out.println("Event was published. Data is " + event.getMyData()); } }
- event를 처리할 method에 @EventListener 어노테이션을 붙여줍니다.
- method이름은 원하는 것으로 설정하면 됩니다.
Spring의 철학에도 맞고 유지보수도 용이해지므로 되도록 4.2부터 지원하는 방법으로 event를 사용하는 것이 좋습니다.
[event 처리 순서]
만약 하나의 event를 처리하는 handler가 여러개 있을 경우 handler는 하나의 thread에서 순차적으로 실행됩니다. 확인을 위해 AnotherHandler.java class를 추가하고 다음과 같이 작성합니다.
@Component public class AnotherHandler { @EventListener public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); //1 System.out.println("Another" + event.getMyData()); } }
- 현재 실행중인 thread를 문자열로 보여줍니다.
MyEvenetHandler.java에도 실행중인 thread를 찍는 구문을 추가합니다.
@Component public class MyEventHandler { @EventListener public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Event was published. Data is " + event.getMyData()); } }
실행해보면 두 handler 모두 동일한 thread에서 순차적으로 실행됨을 확인할 수 있습니다.
[event 처리 순서 지정하기]
특별히 처리 순서를 지정할 필요가 있는 경우에는 handler class에 @Order 어노테이션을 사용합니다. @Order에 지정되는 value는 정수를 지정할 수 있고 그 값이 작을수록 우선순위가 높습니다.
MyEventHandler.java의 handler method에 @Order를 붙여줍니다.
@Component public class MyEventHandler { @EventListener @Order(1) //① public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Event was published. Data is " + event.getMyData()); } }
①@Order 어노테이션의 value를 1로 지정합니다.
AnotherHandler.java의 handler method에도 마찬가지로 @Order를 붙여줍니다.
@Component public class AnotherHandler { @EventListener @Order(3) //① public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Another" + event.getMyData()); } }
①@Order 어노테이션의 value를 3으로 지정합니다.
@Order value 1이 3보다 우선순위가 높기 때문에 MyEventHandler가 AnotherHandler보다 먼저 실행됩니다.
[비동기적인 handler 실행]
handler를 실행할 때 여러개의 thread를 사용하여 비동기적으로 실행하기 원하는 경우에는 @Async 어노테이션을 사용합니다. 비동기적으로 실행되는 경우에는 실행 순서를 보장 할 수 없습니다.
비동기적인 실행을 확인하기 위해 MyEventHandler.java와 AnotherHandler.java의 handler method에 @Async를 붙여줍니다.
@Component public class MyEventHandler { @EventListener @Async public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Event was published. Data is " + event.getMyData()); } }
@Component public class AnotherHandler { @EventListener @Async public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Another" + event.getMyData()); } }
@Async가 동작하도록 하기 위해 Application class에 @EnableAsync를 붙여줍니다. (추가적은 ThreadPool관련 설정이 필요합니다만 이 정도만 해도 비동기로 동작하기는 합니다.)
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
실행해보면 이전과는 달리 main thread가 아닌 다른 thread가 handler별로 실행됨을 확인할 수 있습니다.
[Spring framework에서 제공 Event]
- ContextRefreshedEvent : ApplicationContext를 refresh한 경우 발생하는 event입니다.
- ContextStartedEvent : ApplicationContext의 start() method가 실행되어 life cycle bean들이 시작신호를 받은 시점에 발생하는 event입니다.
- ContextStoppedEvent : ApplicationContext의 stop() method가 식행되어 life cycle bean들이 정지신호를 받은 시점에 발생하는 event입니다.
- ContextClosedEvent : ApplicationContext를 종료하는 close() method가 실행 되어 singlton bean들이 소멸되는 시점에 발생하는 event입니다.
- RequestHandledEvent : HTTP request를 처리했을 때 발생하는 event입니다.
@Component public class MyEventHandler { @EventListener @Async public void handler(MyEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Event was published. Data is " + event.getMyData()); } @EventListener @Async public void handler(ContextRefreshedEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Context Refresh Event"); } @EventListener @Async public void handler(ContextStartedEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Context Started Event"); } @EventListener @Async public void handler(ContextStoppedEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Context Stopped Event"); } @EventListener @Async public void handler(ContextClosedEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Context Closed Event"); } @EventListener @Async public void handler(RequestHandledEvent event) { System.out.println(Thread.currentThread().toString()); System.out.println("Request Handled Event"); } }
'Spring > Spring 핵심 기술' 카테고리의 다른 글
[10] 추상화 - Resource 추상화 (0) 2020.04.21 [09] ApplicationContext - ResourceLoader (0) 2020.04.21 [07] ApplicationContext - MessageSource (0) 2020.04.17 [06] ApplicationContext - EnvironmentCapable interface (0) 2020.04.16 [05] ApplicationContext - Bean의 scope (0) 2020.04.14