2022. 9. 23. 15:56ㆍJAVA/Spring
ApplicationEventPublisher란 무엇인가?
- ApplicationEventPublisher 인터페이스는 이벤트 프로그래밍에 필요한 이벤트를 제공하고 옵저버 패턴의 구현체입니다.
- ApplicationContext 클래스는 ApplicationEventPublisher를 상속받고 있습니다.
- ApplicationEventPublisher를 상속받은 ApplicationContext 클래스는 publishEvent(ApplicationEvent) 메서드를 호출하여 이벤트를 발생시킬 수 있습니다.
1. 이벤트 생성
ApplicationEvent 클래스를 상속받은 이벤트를 생성합니다. 여기서 source는 이벤트가 시작된 객체를 의미하고 data 변수는 이벤트 발생시 같이 보내고자 하는 데이터입니다.
@Getter
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;
}
}
2. 이벤트 발생
주입받은 ApplicationContext 객체가 publishEvent를 호출하여 이벤트를 발생시킵니다.
@Component
public class AppRunner implements ApplicationRunner {
// ApplicationContext 클래스는 ApplicationEventPublisher를 상속받았기 때문에 publishEvent를 사용할 수 있음
@Autowired
ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
// 이벤트를 발생시킴
applicationContext.publishEvent(new MyEvent(this, 100));
}
}
3. 이벤트 처리
발생한 이벤트를 처리하기 위해서 ApplicationListener<이벤트>를 구현한 클래스를 만들어서 빈으로 등록합니다.
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("이벤트를 받았다. 데이터는 " + event.getData() + " 입니다.");
}
}
웹 서버 애플리케이션을 실행하면 실행 결과는 다음과 같습니다.
지금까지 이벤트 정의하고 발생시켜 처리하는 과정이였습니다.
4. @EventListner
스프링 4.2부터는 @EventListener를 사용해서 빈의 메서드에 사용할 수 있게 되었습니다. 기존 MyEvent 클래스를 개선하여 다음과 같이 정의하여 이벤트를 정의할 수 있습니다.
@Getter
public class MyEvent2{
private Object source;
private int data;
public MyEvent2(Object source, int data) {
this.source = source;
this.data = data;
}
}
위와 같은 코드의 특징은 스프링 코드가 없기 때문에 POJO(Plain Old Java Object) 클래스라고 부를 수 있습니다.
이벤트 발생
@Component
public class AppRunner implements ApplicationRunner {
// ApplicationContext 클래스는 ApplicationEventPublisher를 상속받았기 때문에 publishEvent를 사용할 수 있음
@Autowired
ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
// 이벤트를 발생시킴
applicationContext.publishEvent(new MyEvent2(this, 100));
}
}
이벤트 처리
@Component
public class MyEventHandler2{
@EventListener
public void handle(MyEvent2 event) {
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 : 이벤트를 받았다. 데이터는 " + event.getData() + " 입니다.");
}
위와 같이 @EventListener 어노테이션을 적용하여 이벤트 리스너로 정의하고 ApplicationListener<이벤트> 인터페이스를 정의하고 구현할 필요가 없습니다.
5. @Order 어노테이션을 사용하여 이벤트 수행순서를 제어하기
이벤트 발생시 해당 이벤트를 처리하는 핸들러가 여러개라면 @Order 어노테이션을 정의하여 순서를 설정할 수 있습니다.
@Component
public class MyEventHandler2{
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handle(MyEvent2 event) {
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 : 이벤트를 받았다. 데이터는 " + event.getData() + " 입니다.");
}
- @Order(int value) : 정수 value값이 낮을수록 우선순위가 높습니다.
6. @Async 어노테이션을 사용하여 이벤트 처리를 비동기적으로 처리하기
기본적으로 @EventListener 어노테이션 적용시 이벤트 처리가 동기적(synchronized)으로 수행됩니다. 하지만 어떤 이벤트 발생시 해당 이벤트를 처리하는 핸들러가 여러개이고 순서를 따지지 않는다면 @Async 어노테이션을 정의하여 핸들러들을 비동기적으로 수행하게 할 수 있습니다. 단, @Async 어노테이션 적용시 애플리케이션에 @EnableAsync 어노테이션을 정의하여 비동기 수행을 활성화시켜야 합니다.
@Component
public class MyEventHandler2{
@EventListener
@Async // 비동기적으로 수행됨
public void handle(MyEvent2 event) {
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 : 이벤트를 받았다. 데이터는 " + event.getData() + " 입니다.");
}
@SpringBootApplication
@EnableAsync // 비동기 활성화, 단순 @Async만 붙였다고 비동기되지 않음
public class ApplicationEventPublisherApplication {
public static void main(String[] args) {
SpringApplication.run(ApplicationEventPublisherApplication.class, args);
}
}
MyEvent2 클래스 이벤트를 처리하는 핸들러는 MyEventHandler2 클래스 핸들러와 AnotherHandler 클래스 핸들러이고 출력창을 보면 쓰레드가 2개 생성된 것을 볼 수 있습니다. 만약 @Aysnc 어노테이션을 정의하지 않고 수행하게 되면 싱글 쓰레드로 핸들러들을 처리하게 됩니다.
7. 스프링이 제공하는 기본 이벤트
- ContextRefreshedEvent : ApplicationContext를 초기화 하거나 새로고침했을때 발생
- ContextStartedEvent : ApplicationContext를 start()하여 라이프 사이클 빈들이 시작 신호를 받는 시점에 발생.
- ContextStoppedEvent : ApplicationContext를 stop()하여 라이프사이클 빈들이 정지 신호를 받은 시점에 발생
- ContextClosedEvent : ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에 발생
- RequestHandledEvent : HTTP 요청을 처리했을 때 발생.
다음 코드는 MyEventHandler2 클래스 핸들러에 기본 이벤트를 정의한 것입니다.
@Component
public class MyEventHandler2{
@EventListener
// @Order(Ordered.HIGHEST_PRECEDENCE)
@Async // 비동기적으로 수행됨
public void handle(MyEvent2 event) {
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 : 이벤트를 받았다. 데이터는 " + event.getData() + " 입니다.");
}
@EventListener
@Async
public void handle(ContextRefreshedEvent event){
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 ContextRefreshedEvent");
}
@EventListener
@Async
public void handle(ContextStartedEvent event){
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 ContextStartedEvent");
}
@EventListener
@Async
public void handle(ContextClosedEvent event){
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 ContextClosedEvent");
}
@EventListener
@Async
public void handle(RequestHandledEvent event){
System.out.println(Thread.currentThread());
System.out.println("MyEventHandler2 RequestHandledEvent");
}
}
수행결과는 다음과 같습니다.
References
source code : https://github.com/yonghwankim-dev/spring_study/tree/master/application_event_publisher/src/main/java/kr/yh
[인프런] 스프링 프레임워크 핵심 기술
'JAVA > Spring' 카테고리의 다른 글
[Spring] ResourceLoader 인터페이스 (0) | 2022.09.30 |
---|---|
[Spring] Resource 인터페이스와 Resource 인터페이스 구현체 (0) | 2022.09.25 |
[Springboot] 내장 웹 서버(Embedded Web Servers) #1 (0) | 2022.09.23 |
[Spring][IoC] MessageSource (0) | 2022.09.13 |
[Spring][IoC] Environment, 프로파일 및 프로퍼티 (0) | 2022.09.01 |