[Spring][IoC] ApplicationEventPublisher

2022. 9. 23. 15:56JAVA/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
[인프런] 스프링 프레임워크 핵심 기술