ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring event
    Spring 2020. 3. 16. 11:46

    https://www.baeldung.com/spring-events

     

    Spring Events | Baeldung

    The Basics of Events in Spring - create a simple, custom Event, publish it and handle it in a listener.

    www.baeldung.com

    스프링 이벤트를 구현하는 방법.

     

    1. 이벤트는 ApplicationEvent 를 상속한다.

    2. 퍼블러셔는 ApplicationEventPublisher 를 주입받는다.

    3. 리스너는 ApplicationLister interface 를 구현한다.

     

    간단한 event 만들기

    public class CustomSpringEvent extends ApplicationEvent {
        private String message;
     
        public CustomSpringEvent(Object source, String message) {
            super(source);
            this.message = message;
        }
        public String getMessage() {
            return message;
        }
    }

    Publisher 만들기

    @Component
    public class CustomSpringEventPublisher {
        @Autowired
        private ApplicationEventPublisher applicationEventPublisher;
     
        public void doStuffAndPublishAnEvent(final String message) {
            System.out.println("Publishing custom event. ");
            CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
            applicationEventPublisher.publishEvent(customSpringEvent);
        }
    }

    퍼블리셔는 리스너들이 가져갈 수 있는 이벤트를 생산한다. 

    위와 같은 방법도 있고, ApplicationEventPublisherAware interface 를 구현하는 것도 가능하다.

    하지만 위 방법이 더 간편하다.

     

    Listener 만들기

    @Component
    public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
        @Override
        public void onApplicationEvent(CustomSpringEvent event) {
            System.out.println("Received spring custom event - " + event.getMessage());
        }
    }

    필요한 건 ApplicationListener 를 구현한 것이다.

    spring event 는 기본적으로 동기식으로 처리된다. doStuffAndPublishAnEvent 는 이벤트가 처리될 때까지 리스너들을 block 한다.

     

    그렇다면 비동기식으로는 어떻게?

     

    ApplicationEventMuilticaster 를 만들어주고 가능하게 할 수 있다. 이를 통해 SimpleAsyncTaskExecutor 를 이용한다.

     

    @Configuration
    public class AsynchronousSpringEventsConfig {
        @Bean(name = "applicationEventMulticaster")
        public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
            SimpleApplicationEventMulticaster eventMulticaster =
              new SimpleApplicationEventMulticaster();
             
            eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
            return eventMulticaster;
        }
    }

    설정한 다른 것들은 그대로 두고, 위 설정만 추가한다. 그러면 리스너는 쓰레드와 분리하여 비동기식으로 동작한다.

     

    기존에 스프링에서 기본적으로 존재하는 이벤트들을 리스닝 하고 싶다면?

    (ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent ... etc)

     

    추가적으로 스프링 4.2 버전 이상이라면?

    리스너를 어노테이션을 이용하여 적용가능하다.

     

    @Component
    public class AnnotationDrivenContextStartedListener {
        // @Async
        @EventListener
        public void handleContextStart(ContextStartedEvent cse) {
            System.out.println("Handling context started event.");
        }
    }

    굉장히 간편하고, 비동기를 적용하고 싶다면 주석처리해놓은 부분처럼 Async 어노테이션을 이용하면 된다.

     

    제네릭을 이용한 보편적인 이벤트 타입을 만드는 법

     

    public class GenericSpringEvent<T> {
        private T what;
        protected boolean success;
     
        public GenericSpringEvent(T what, boolean success) {
            this.what = what;
            this.success = success;
        }
        // ... standard getters
    }

    위와 같이 구성하면 된다. 문서 가장 윗부분의 ApplicationEvent 를 상속받은 이벤트와 차이가 있다.

    우리가 유연한 커스텀 이벤트를 만드는 것이기 때문에 상속이 필요하지 않다.
    (필요하지 않다?... 보단 상속하지 않고 유연하게 만들겠다 라는 표현이 나을듯)

     

    리스너 만들기

    @Component
    public class GenericSpringEventListener 
      implements ApplicationListener<GenericSpringEvent<String>> {
        @Override
        public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
            System.out.println("Received spring generic event - " + event.getWhat());
        }
    }

     

    근데 이렇게 하면 GenericSpringEvent 가 ApplicationEvent 를 상속받아야만 한다. 뭐지?

    위에서 상속이 필요하지 않다라고 해놓고선... 
    어노테이션을 이용한 방법을 써서 상속 받지 않고 해결이 가능하댄다..(그럼 왜 가르쳐준거지...)

     

    @Component
    public class AnnotationDrivenEventListener {
        @EventListener(condition = "#event.success")
        public void handleSuccessful(GenericSpringEvent<String> event) {
            System.out.println("Handling generic event (conditional).");
        }
    }

    그리고 추가로 SpEL 이라는 expression 을 이용하여, 이벤트 리스너의 조건 설정이 가능하다.

    SpEL 에 대해서는 다른 문서에서 다룬다고 한다.

     

    러블리셔의 경우는 위에서 언급한 것과 같지만, 이벤트의 제네릭 부분만 신경써서 해주면 된다.

    신경써서 해준다는게 조금 두리뭉실하다. 간단하게 말하면 위의 예제에서는 GenericSpringEvent<String> 을 상속받으면 된다.

     

    또한 이벤트를 collection 에 담아서 여러 이벤트를 들고 다니는 방식도 구현이 가능하다.

     

    트랜잭션 안에서의 이벤트?

     

    스프링 4.2 부터 @TransactionalEventListener 을 이용하여 트랜잭션 안에서의 동작을 정의할 수 있다.

    AFTER_COMMIT (default) is used to fire the event if the transaction has completed successfully
    AFTER_ROLLBACK – if the transaction has rolled back
    AFTER_COMPLETION – if the transaction has completed (an alias for AFTER_COMMIT and AFTER_ROLLBACK)
    BEFORE_COMMIT is used to fire the event right before transaction commit
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void handleCustom(CustomSpringEvent event) {
        System.out.println("Handling event inside a transaction BEFORE COMMIT.");
    }

    감사합니다.

    반응형

    'Spring' 카테고리의 다른 글

    Reactive Spring Boot: Part 1: Kotlin REST Service  (0) 2021.01.17
    Spring kafka 적용하기  (0) 2020.03.11
Designed by Tistory.