Spring/Spring 핵심 기술

[08] ApplicationContext - ApplicationEventPublisher

낙타선생 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
    }
}
  1. ApplicationEventPublisher bean을 주입받습니다.
  2. 현재 객체(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());
    }
}
  1. event handler는 bean으로 등록합니다.
  2. 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());
    }
}
  1. event를 처리할 method에 @EventListener 어노테이션을 붙여줍니다.
  2. 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());
    }
}
  1. 현재 실행중인 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에서 순차적으로 실행됨을 확인할 수 있습니다.

하나의 thread를 통해 순차적으로 실행되는 event handler들

 

 

 

[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 실행]

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별로 실행됨을 확인할 수 있습니다.

비동기적인 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");
    }
}