[Spring] ApplicationEventPublisher를 통한 스프링 이벤트 처리(ApplicationEventPublisher, Spring Event Processing)

2021. 1. 14. 03:42 Spring Framework/Spring 입문 - 개념 및 핵심

| 스프링 ApplicationEventPublisher

 

스프링 ApplicationEventPublisher는 스프링에서 이벤트 프로그래밍에 필요한 인터페이스를 제공한다. ApplicationContext 인터페이스에 이미 상속되어있어서 ApplicationContext의 구현체에서도 접근이 가능하다.

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationContext applicationContext;  // ApplicationEventPublisher를 사용해도 된다.

    @Override
    public void run(ApplicationArguments args) throws Exception {
        applicationContext.publishEvent(new MyEvent(this, 100));
        ((ConfigurableApplicationContext)applicationContext).close();
    }
}

 

이벤트 등록 및 수신방법

 

publishEvent 메서드는 ApplicationEventPublisher가 제공하는 메서드이며 이 메서드를 통해 스프링 기반 어플리케이션에 이벤트를 발생시킬 수 있다.

public class MyEvent {

    private int data;

    private Object source;

    public MyEvent(Object source, int data){
       this.source = source;
       this.data = data;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }
}

 

이벤트는 POJO 스타일로 만들 수 있다. 원래는 ApplicationEvent를 상속받아 만들어야 했지만 Spring 4.2부터는 클래스를 상속받지 않아도 이벤트로 사용할 수 있다.

@Component
public class MyEventHandler {

  @EventListener
  public void handle(MyEvent event){
    System.out.println("Event received " + event.getData());
  }
}

 

 MyEvent를 수신하는 방법은 스프링 빈으로 등록된 클래스의 메소드에 @EventListener를 등록, 위에 작성했던 MyEvent를 인자로 받는 메서드를 작성하면 된다.

 

@SpringBootApplication
public class SpringTutorialApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringTutorialApplication.class, args);
  }
}

스프링부트 어플리케이션을 실행시키면 다음과 아래와 같은 출력이 뜬다.

Thread[main,5,main]
Event received 100

 

| 이벤트 수신 순서 정하는 @Order

 

여러 핸들러를 추가하고 난 후 그 Handler의 이벤트를 수신할 시 순서를 정하고 싶으면 @Order 어노테이션을 붙여야 한다. @Order 어노테이션 안의 HIGHEST_PRECEDENCE Integer.MIN_VALUE로 값이 낮을 수록 우선순위를 먼저 점한다는 의미를 가지고 있다. 따라서 낮은 값에서 높은 값 순서로 이벤트의 우선순위를 점할 수 있다.

@Component
public class AnotherHandler {

  @EventListener
  @Order(Ordered.HIGHEST_PRECEDENCE + 1)
  public void handle(MyEvent myEvent){
    System.out.println(Thread.currentThread().toString());
    System.out.println("Another " + myEvent.getData());
  }
}
@Component
public class MyEventHandler {

  @EventListener
  @Order(Ordered.HIGHEST_PRECEDENCE + 2)
  public void handle(MyEvent event){
    System.out.println(Thread.currentThread().toString());
    System.out.println("Event received " + event.getData());
  }
}
Thread[main,5,main]
Another 100
Thread[main,5,main]
Event received 100

 

| 비동기적인 이벤트 수신을 위한 @Async

 

비동기적으로 이벤트를 수신하기 위해서는 스프링부트 어플리케이션이 실행되는 main 진입점에 @EnableAsync 어노테이션을 붙여야 한다.

@SpringBootApplication
@EnableAsync
public class SpringTutorialApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringTutorialApplication.class, args);
    }
}

 

다음 @EventListener 어노테이션이 붙은 매서드에 @Async를 붙여 이 메서드가 비동기적으로 메세지를 수신할 것이라는 것을 명시해야 한다. 이때 비동기적으로 메세지를 수신하므로 @Order 어노테이션은 의미가 없어진다.

@Component
public class AnotherHandler {

  @EventListener
  @Async
  public void handle(MyEvent myEvent){
    System.out.println(Thread.currentThread().toString());
    System.out.println("Another " + myEvent.getData());
  }
}
Thread[main,5,main]
Event received 100
Thread[task-1,5,main]
Another 100

 

| 스프링이 제공하는 기본 이벤트

 

  • ContextRefreshedEvent : ApplicationContext를 초기화 했거나 리프래시 했을 때 발생.
  • ContextStartedEvent : ApplicationContext를 start()하여 라이프사이클 빈들이 시작 신호를 받은 시점에 발생
  • ContextStoppedEvent : ApplicationContext를 stop()하여 라이프사이클 빈들이 정지 신호를 받은 시점에 발생
  • ContextClosedEvent : ApplicationContext를 close() 하여 싱글턴 빈들이 소멸되는 시점에 발생.
  • RequestHandledEvent : HTTP 요청을 처리했을 때 발생.
@Component
public class MyEventHandler {

    @EventListener
    @Async
    public void handle(ContextRefreshedEvent event){
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextRefreshEvent");
    }

    @EventListener
    @Async
    public void handle(ContextStartedEvent event){
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextStartedEvent");
    }

    @EventListener
    @Async
    public void handle(ContextStoppedEvent event){
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextStoppedEvent");
    }

    @EventListener
    @Async
    public void handle(ContextClosedEvent event){
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextClosedEvent");
    }
}
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationContext applicationContext;  // ApplicationEventPublisher를 사용해도 된다.

    @Override
    public void run(ApplicationArguments args) throws Exception {
        applicationContext.publishEvent(new MyEvent(this, 100));
        ((ConfigurableApplicationContext)applicationContext).start();
        ((ConfigurableApplicationContext)applicationContext).stop();
        ((ConfigurableApplicationContext)applicationContext).close();
    }
}
2018-12-13 12:06:14.841  INFO 14100 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1019 ms
2018-12-13 12:06:15.077  INFO 14100 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
Thread[task-1,5,main]
ContextRefreshEvent
2018-12-13 12:06:15.234  INFO 14100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-13 12:06:15.237  INFO 14100 --- [           main] c.s.s.SpringTutorialApplication          : Started SpringTutorialApplication in 1.728 seconds (JVM running for 2.35)
Thread[task-2,5,main]
ContextStartedEvent
Thread[task-3,5,main]
ContextStoppedEvent
Thread[task-4,5,main]
ContextClosedEvent
2018-12-13 12:06:15.243  INFO 14100 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'



출처: https://engkimbs.tistory.com/718?category=767795 [새로비]