ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [08] ApplicationContext - ApplicationEventPublisher
    Spring/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
        }
    }
    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");
        }
    }

    댓글

Designed by Tistory.