ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ApplicationEventPublisher와 EventListener
    개발/Spring(boot) 2024. 12. 1. 16:30

    업무를 하던 도중 사용자의 특정 행동을 감지하고 저장하는데 두 기능을 사용하고 있어서 해당 내용에 대해 조금 더 알아보고 작성해보고자 한다.

     

    먼저 알아보기 전 회사 코드를 보고 추측해봤던 두 코드의 기능은 다음과 같다.

    1. ApplicationEventPublisher는 이벤트를 Publish하고 그 이후에 행동에는 책임을 지지 않는다.

    2. EventListener는 원하는 이벤트가 발생했을 때 해당 이벤트를 감지하고 이후 비즈니스 로직을 처리한다.

     

    여기까지가 추측했던 동작 방식이고 이제부터는는 실제로 해당 기능을 찾아보며 조금 더 구체적이고 새롭게 알게된 내용을 작성해보고자 한다.


    ApplicationEventPublisher의 내부 구조는 다음과 같다.

    @FunctionalInterface
    public interface ApplicationEventPublisher {
    
    	default void publishEvent(ApplicationEvent event) {
    		publishEvent((Object) event);
    	}
    
    	void publishEvent(Object event);
    }

    생각보다 매우 간단했고 @FunctionalInterface라는 처음 보는 어노테이션이 보였고,

    @FunctionalInterface는 하나의 추상 메서드를 가진다는 정보를 전달하기 위한 어노테이션이라고 한다.

     

    그래서 ApplicationEventPublisher는 publishEvent라는 메서드만을 가지는 것으로 보인다.

    그리고 default method 또한 처음 봤는데

    이는 인터페이스를 상속받을 때 특정 메서드를 구현 클래스에서 반드시 구현할 필요가 없도록 한다고 한다.

     

    ApplicationEventPublisher라는 인터페이스를 상속받을 때 꼭 publishEvenet를 구현할 필요 없도록 하기 위해 default void publishEvent()가 존재한다고 이해하면 될 것 같다.

     

    이제 publishEvent 메서드에 대해 알아보고자 한다.

    생각보다 동작은 간단한데 애플리케이션에 등록된 모든 일치하는 리스너에게 애플리케이션 이벤트를 알린다고 한다.

    이렇게 어떤 이벤트를 publish하고싶을 때 ApplicationEventPublisher를 사용하면 된다고 한다.

    또한 Mono.fromRunnable(() -> eventPublisher.publishEvent(...)) 또는

    @Async를 사용해 비동기적으로도 이벤트를 발행할 수 있지만,

    TransactionalEventPublisher를 통해 좀 더 편하게 비동기적으로 이벤트를 다룰 수 있다고 한다.

     

    여기까지가 이벤트를 발생시키는 ApplicationEventPublisher에 대한 내용이다.

    그러면 발생한 이벤트는 어떻게 처리해야 할까? 이를 처리하기 위한 EventListener에 대해 알아보자.


    EventListenr 내부 구조는 아래와 같다.

    @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 "";
    }

    인터페이스였던 ApplicationEventPublisher와 다르게 EventListenr는 Annotation 방식으로 되어있다.

     

    설명을 읽어보면 classes와 value는 동일하고 ApplicationEventPublisher의 Event 중

    특정 class로 publish된 이벤트에 대한 동작을 처리할 지를 정의하는 코드이다.

    그래서 실제로 사용할 때는 아래와 같이 사용하면 된다.

    @EventListener(value = UserCreateEventHandler.class)
    public void handleCreate(UserCreateEventHandler event) {
        ...
    }

    value와 classes의 default는 모든 이벤트에 대한 처리를 하는것 이라고한다.

     

    또한 condition이라는 값도 있는데 이는 Spring Expression Language (SpEL) 표현식에 대한 처리를 한다고 한다.

    SpEL(Spring Expression Language)은 Spring 프레임워크에서 제공하는 강력한 표현식 언어이다.

    SpEL을 사용하면 Java 객체를 다루거나 Spring의 컨테이너 내에서 정의된 빈(bean)들을 쉽게 참조하고 조작할 수 있다.

    이 언어는 XML, 애너테이션 기반 설정, Java 코드 등 다양한 곳에서 동적으로 값을 평가하고 표현식을 작성하는 데 사용된다.

     

    정규 표현식과 유사한 Springboot에서 정의해놓은 표현식 정도로 이해했다.

     

    추가적으로 @Order 어노테이션으로 특정 이벤트에 대한 리스너를 호출할 순서를 정의하는 것도 가능하고

    @Async로 비동기 이벤트 처리를 할 수 있으며 좀 더 편하게 도와주는 @TransactionalEventListener가 존재한다.


    오늘은 ApplicationEventPublisher와 EventListener에 대해 작성해보았다.

    사실 해당 내용에 대해 작성하면서 스프링 단일 서버에서 이벤트를 처리하는 방법에 대해 알게 되었고

    Autoscaling 과정에서 서버가 Scale-out 되거나 비정상적으로 종료된 경우를 대비해 Message-queue 방식을 사용하는 것이 아닌가?라는 추측을 가지게 되었다.


    참조

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationEventPublisher.html

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/event/EventListener.html

    반응형

    '개발 > Spring(boot)' 카테고리의 다른 글

    조직도를 만들어보자  (1) 2024.12.08
    Springboot Redis 세션 만료 감지하기  (0) 2024.11.24

    댓글

Designed by Tistory.