spring / / 2023. 11. 7. 08:58

스프링 이벤트

이 글은 아래 링크를 정리한 자료이다.

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

1. 개요

스프링 이벤트는 스프링에서 많은 주목을 받지 않는 것 중 하나이지만 가장 유용한 기능 중 하나이다. 스프링에서 제공하는 많은 기능을 가진 ApplicationContext에서 제공하는 기능 중 하나이다.

여기서 지켜야할 몇 가지 규칙이 있다.

  • 이벤트 클래스는 스프링 프레임워크 4.2 이전 버전을 사용한다면 ApplicationEvent를 확장해야 한다. 4.2부터는 더 이상 ApplicationEvent를 확장할 필요가 없다.
  • 퍼블리셔는 ApplicationEventPublisher를 주입(inject)해야 한다.
  • 리스너는 ApplicationListener 인터페이스를 구현해야 한다.

2. Custom Event

기본적으로 스프링은 이벤트를 생성하고 발행(publish)하는 것을 동기방식으로 한다. 이 방식은 리스너가 퍼블리셔의 트랜잭션에 참여할 수 있는 것과 같은 몇 가지 장점이 있다.

2.1 Simple Application 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;
    }
}

2.2 Publisher

이제, 이벤트 퍼블리셔(publisher)를 만들어보자. 퍼블리셔는 이벤트 객체를 구성하고 구독하는 대상에 이벤트를 발행(publish)한다.

이벤트를 발행하기 위해서, 퍼블리셔는 단순히 ApplicationEventPublisher를 주입(inject)하고 publishEvent() API를 사용할 수 있다.

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishCustomEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

또는, 퍼블리셔 클래스에서 ApplicationEventPublisherAware 인터페이스를 구현하고 애플리케이션이 시작될 때 이벤트 퍼블리셔를 주입(inject) 할 수도 있다. 일반적으로 @Autowire로 퍼블리셔를 주입(inject)하는게 더 쉽다.

스프링 프레임워크 4.2부터 ApplicationEventPublisher 인터페이스는 이벤트의 어떤 타입의 객체로도 받을 수 있는 publishEvent(Object event)의 새로운 메소드를 제공한다. 그러므로 스프링 이벤트는 더 이상 ApplicationEvent 클래스를 확장할 필요가 없다.

2.3 Listener

마지막으로, 리스너를 만들어보자.

리스너를 만들기 위한 최소한의 조건은 스프링 빈이어야 하고 ApplicationListener 인터페이스를 구현해야 한다는 것이다.

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

우리가 만든 custom 리스너는 onApplicationEvent 메소드를 type-safe하게 만드는 generic 타입의 파라미터를 가지고 있다는 것을 명심하자. 이것은 또한 객체가 특정 이벤트 클래스의 인스턴스이고 캐스팅 하지 않아도 된다.

그리고 이전에 말했듯이(기본적으로 스프링 이벤트는 동기이다) doStuffAndPublishAnEvent 메소드는 모든 리스너가 이벤트를 처리할 때까지 블로킹을 한다.

3. 비동기 이벤트 생성하기

어떤 경우에는 이벤트를 동기방식이 아닌 비동기로 사용할 필요가 있다.

우리는 ApplicationEventMulticaster 빈을 생성할 때 설정부분에서 executor를 바꿀 수도 있다.

여기서 우리 목적에 맞게 SimpleAsyncTaskExecutor 는 잘 동작한다.

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster =
          new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

이벤트, 퍼블리셔(publisher) 그리고 리스너(listener)는 이전과 동일하지만 리스너는 다른 쓰레드에서 이벤트를 비동기로 처리할 것이다.

4. 기존 프레임워크 이벤트

스프링은 여러가지 이벤트를 독립적으로 발행한다. 예를 들면, ApplicationContext 는 여러가지 프레임워크 이벤트를 작동시킨다. ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent

이 이벤트들은 애플리케이션 개발자들에게 애플리케이션의 생명 주기의 특정 위치에서 실행 시킬 수 있는 옵션을 제공하고 필요한 곳에 로직을 추가할 수 있게 도와준다.

여기 context refresh 이벤트를 수신하는 리스너의 간단한 예제가 있다.

public class ContextRefreshedListener 
  implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent cse) {
        System.out.println("Handling context re-freshed event. ");
    }
}

기존 프레임워크 이벤트에 대해 좀 더 알고 싶다면 여기를 참고하라. (https://www.baeldung.com/spring-context-events)

5. Annotation 기반 이벤트 리스너

스프링 4.2로 시작할 때 이벤트 리스너(listener)는 ApplicationListener 인터페이스를 구현하는 빈(bean)일 필요는 없다 - @EventListener 어노테이션으로 관리되는 빈으로 public 메소드로 등록될 수 있다.

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

이전과 같이 메소드 시그니처는 소비되는 이벤트 타입을 선언한다.

기본적으로 리스너(listener)는 동기로 실행된다. 하지만 @Async 어노테이션을 추가함으로써 비동기로 쉽게 바꿀 수 있다. 애플리케이션에서 Async를 활성화해야 한다.

6. Generics Support

이벤트 타입에서 generics 정보로 이벤트를 전파하는 것도 가능하다.

6.1 Generic Application Event

Generic 이벤트 타입을 만들어보자.

예제에서, 이벤트 클래스는 특정 컨텐츠를 가지고 있고 success 상태를 나타내는 것이다.

public class GenericSpringEvent<T> {
    private T what;
    protected boolean success;

    public GenericSpringEvent(T what, boolean success) {
        this.what = what;
        this.success = success;
    }
    // ... standard getters
}

GenericSpringEventCustomSpringEvent의 차이점을 보자. 이제 어떤 유형의 이벤트도 발핼할 수 있고 ApplicationEvent를 더 이상 확장할 필요가 없다.

6.2 Listener

이벤트 리스너를 만들어 보자.

이전과 같이 ApplicationListener 인터페이스를 구현함으로써 리스너를 정의할 수 있다.

@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());
    }
}

하지만 이 정의는 불행하게도 ApplicationEvent 클래스에서 GenericSpringEvent 상속이 필요하다. 그래서 여기에서는, 이전에 논의된 어노테이션 방식의 이벤트 리스너를 사용해보자.

@EventListener 어노테이션에 SpEL 표현식을 정의함으로써 이벤트 리스너를 조건식으로 만들 수도 있다.

이 경우에 이벤트 핸들러는 문자 형식의 GenericSpringEvent에 대해서만 실행될 것이다.

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

Spring Expression Language (SpEL) 는 강력한 표현 언어이다.

6.3 Publisher

이벤트 퍼블리셔는 위에서 나타낸 것과 유사하다. 타입을 지정하지 않고 사용할 수 있어서 Generics 파라미터를 처리할 이벤트를 발행할 필요가 있다. 예를 들면 GenericStringSpringEventGenericSpringEvent<String>를 상속한다.

또한 이벤트를 발행하는 다른 방법이 있다. @EventListener 를 가진 메소드에 non-null을 리턴한다면 스프링 프레임워크는 새로운 이벤트를 보낼 것이다. 게다가 이벤트 처리 결과로 컬렉션을 리턴함으로써 새로운 이벤트를 발행할 수 있다.

7. 트랜잭션 기반 이벤트

이 장은 @TransactionalEventListener 어노테이션 사용에 관한 내용이다. 트랜잭션 관리에 대해 좀 더 알고 싶다면 Transactions With Spring and JPA를 확인해라.

스프링 4.2부터 프레임워크는 새로운 @TransactionalEventListener 어노테이션을 제공한다. @EventListener의 확장이며 트랜잭션 단계에 이벤트 리스너를 연결한다.

바인딩은 다음과 같은 트랜잭션 단계에 가능하다.

  • AFTER_COMMIT (default)는 트랜잭션이 완료되었을 때 이벤트를 발행한다.
  • AFTER_ROLLBACK – 트랜잭션이 롤백되었을 때
  • AFTER_COMPLETION – 트랜잭션이 완료되었다면 (AFTER_COMMIT and AFTER_ROLLBACK의 별칭)
  • BEFORE_COMMIT 트랜잭션 커밋 바로 전에 이벤트를 발행하기 위해 사용된다.

다음은 transaction 이벤트 리스너의 간단한 예이다.

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
    System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}

이벤트 프로듀서가 실행중이고 커밋되어야 하는 트랜잭션이 있는 경우에만 리스너가 실행될 것이다.

그리고 실행되는 트랜잭션이 없다면 fallbackExecution 속성을 true로 설정함으로써 오버라이드 하지 않은 이벤트는 전송되지 않는다.

참고

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

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유