EventListener

Annoation that marks a method as a listener for application events.
-Stephane Nicoll, Sam Brannen-

  • 메서드를 애플리케이션 이벤트의 리스너로 표시하는 애노테이션입니다.
  • If an annotated method supports a single event type, the method may declare a single parameter that reflects the event type to listen to.
  • If an annotated method supports multiple event types, this annotation may refer to one or more supported event types using the classes attribute.
  • 이벤트 리스너는 단일 및 여러 이벤트를 지원할 수 있다.
    • 타겟은 메서드, 유지 범위는 런타임이다.

    • 주로 단일 이벤트 핸들링을 하겠지만, 여러 이벤트 핸들링을 해야한다면, classes 조건을사용할 것 같습니다.

      @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface EventListener {
      
      		@AliasFor("classes")
      		Class<?>[] value() default {};
      	
      		@AliasFor("value")
      		Class<?>[] classes() default {};
      	
      		String condition() default "";
      	
      		String id() default "";
      }
      @Component
      public class AppEventListener {
      
          @EventListener(classes = {PlayEvent.class, WorkEvent.class})
          public void handleEvent() {
              System.out.println("if play or work event on I'm going to work out!");
          }
      }
      @Slf4j
      @RequiredArgsConstructor
      @RestController
      public class TestController {
      
          private final ApplicationEventPublisher applicationEventPublisher;
      
          @GetMapping("/event")
          public void onEvent() {
            log.info("event on!!!!");
            applicationEventPublisher.publishEvent(new PlayEvent());
            log.info("another event on!!!");
            applicationEventPublisher.publishEvent(new WorkEvent());
          }
      }
      2023-08-04 20:17:59.353  INFO 2350 --- [nio-8080-exec-3] e.f.f.event.TestController               : event on!!!!
      if play or work event on I'm going to work out!
      2023-08-04 20:17:59.353  INFO 2350 --- [nio-8080-exec-3] e.f.f.event.TestController               : another event on!!!
      if play or work event on I'm going to work out!

USAGE (MY OPINION)

  • 동영상 컨텐츠를 조회하는 로직외부 데이터 수집 플랫폼으로 동영상 시청자의 성별 정보를 전송하는 로직이 한 비즈니스 로직에 같이 있다고 가정해봅시다.
  • 시청자는 빠른 동영상 조회가 우선이지 정보 전송에 관한 것은 관심사가 아닙니다.
  • 하지만, 정보 전송 로직에 장애가 생긴다면? (느려지거나, 예외가 나거나……. 등등)
    • 동영상 컨텐츠 조회라는 우선순위가 높은 기능이 제공되지 못할 것입니다.
    • 시청자는 하염없이 기다릴수도?
  • 두 기능의 결합도를 낮추는 방법 중 하나가 이벤트입니다.
  • ApplicationEventPublisher 를 사용해서 ApplicationEventpublish 해주고 이 Event 를 핸들링할 EventListener 를 구현해줍니다.
    • 이 때 사용되는 것이 EventListener, TransactionalEventListener 입니다.

POINT

디테일한 사항에 대한 레퍼런스입니다.

  • 리스너가 이벤트를 비동기적으로 처리하도록 하려면 @Async 를 사용할 수 있지만! 유의할 점
    • 비동기 이벤트 리스너가 예외를 발생시키면 호출자에게 전파되지 않는다.
    • AsyncUncaughtExceptionHandler
    • 비동기 이벤트 리스너 메서드는 값을 반환하여 후속 이벤트를 게시할 수 없다.
  • 처리 결과로 다른 이벤트를 게시해야 하는 경우
    • ApplicationEventPublisher 를 넣어 이벤트를 수동으로 게시한다.
  • 특정 이벤트에 대한 리스너가 호출되는 순서를 정의하려면?
    • @Order 를 추가한다.
    • 스프링은 숫자가 낮을수록 우선순위!
  • 많은 로직들이 트랜잭션을 타고 수행되는데 트랜잭션에 관련된 이벤트 의 디테일한 처리를 할 수가 없네?
    • 이 때 TransactionalEventListener 를 사용합니다.

TransactionalEventListener

An EventListener that is invoked according to a TransactionPhase.

  • Stephane Nicoll, Sam Brannen, Oliver Drotbohm
  • TransactionPhase 에 따라 호출되는 EventListener 입니다.
    • EventListener 와 사용방법은 같습니다.

차이점

  • If a transaction is running, the event is handled according to its TransactionPhase.
    • 트랜잭션 Phase 에 따라 이벤트를 핸들링할 수 있습니다.

USAGE (FEAT : MY OPINION)

  • AFTER_COMMIT : 트랜잭션 결과를 커밋하고 난 시점에 이벤트가 실행 (default)
    • 결과가 꼭 저장되고 난 후에 실행되야 하는 경우 (데이터 정합성 보장)
  • AFTER_ROLLBACK : 트랜잭션에 롤백이 일어난 시점에 이벤트가 실행
    • 롤백이 일어났다면 예외나 에러가 생겼다는 경우인데 이를 따로 핸들링하는 경우?
  • AFTER_COMPLETION : 트랜잭션이 끝났을 때(커밋 or 롤백) 이벤트 실행
  • BEFORE_COMMIT :트랜잭션이 커밋되기 전에 이벤트 실행
    • ex) 컨텐츠 접근 이력 저장 (컨텐츠를 봤던 안봤던 접근 이력 저장)
    • 성능 상은 가장 빠르겠죠? 하지만 정합성은 맞지 않을 위험성

POINT

  • 트랜잭션 이벤트 리스너는 PlatformTransactionManager 에서 관리하는 스레드 바운드 트랜잭션에서만 작동한다!
  • ReactiveTransactionManager 에 의해 관리되는 Reactive Transaction 은 스레드 로컬 변수 대신 Reactor 컨텍스트를 사용
  • 이벤트 리스너 관점에서 참여할 수 있는 호환 가능한 활성 트랜잭션이 없다.
  • 무려 경고라고 되어있는 부분
    • TransactionPhase.AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION 으로 설정된 경우 커밋되었거나 롤백되었지만 트랜잭션 리소스는 여전히 활성 상태고 액세스 할 수 있다!!!!!!!!
  • Webflux 사용했을때 TransactionalEventListener 가 생각했던데로 작동하지 않았던 문제점이 있었다.
    • 트랜잭션과 Webflux 데이터 방출 (Mono) 의 타이밍이 맞지 않아 고생했던 경험이 있었는데 이 부분에서 경고해주고 있었다.
    • 레퍼런스를 잘 읽자!

Summary.

  • 이벤트 + 비동기를 조합하면 로직간 결합도 및 대량 트래픽시 성능을 향상시킬수 있습니다.
  • 스프링에서는 EventListener, TransactionalEventListener, ApplicationEventPublisher, @Async Etc…….. 사용해서 구현할 수 있습니다.
  • 하지만, 비동기 처리가 들어가면 유의할 점이 매우! 많아지니 조심하고 디테일하게 테스트 및 레퍼런스 체크 후 사용해야할 것을 사용할 때마다 느끼고 있습니다.
  • 틀린점이나 피드백 있으시면 댓글 달아주시면 감사하겠습니다 😀
profile
ProblemOverFlow

1개의 댓글

comment-user-thumbnail
2023년 8월 4일

정보에 감사드립니다.

답글 달기
Powered by GraphCDN, the GraphQL CDN