[Spring] Spring Events 이란?

2024. 12. 22. 23:51·Spring

이벤트란 무엇인가?


Spring 프레임워크에서 이벤트는 ApplicationContext가 제공하는 기능 중 하나로, 특정 상황에서 이벤트를 발행(publish)하고 이를 처리할 수 있도록 지원합니다. 이 기능은 크게 아래 세 가지로 구성됩니다.

  1. 이벤트(Event)
  2. 발행자(Publisher)
  3. 리스너(Listener)

1. Event (이벤트)

Spring은 사용자 정의 이벤트를 생성하고 발행할 수 있도록 지원합니다. 기본적으로 이러한 이벤트는 동기식으로 동작합니다.

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. 발행자 (Publisher)

발행자는 이벤트 객체를 생성하고, 이를 듣고 있는 모든 리스너에게 발행합니다.

ApplicationEventPublisher 사용

발행자는 ApplicationEventPublisher를 주입받아 publishEvent() 메서드를 통해 이벤트를 발행할 수 있습니다.

@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 인터페이스를 구현하여 사용할 수도 있습니다.

 

3. 리스너 (Listener)

리스너는 특정 이벤트를 처리하기 위해 등록됩니다. 이를 구현하기 위해서는 해당 클래스를 Spring 빈(Bean)으로 등록하고, ApplicationListener 인터페이스를 구현해야 합니다.

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}
  • 작성된 리스너는 제네릭 타입으로 매개화되어 있으며, 이를 통해 타입 안전성을 제공합니다.
  • 타입 확인이나 형변환을 할 필요가 없습니다.

비동기식 이벤트


기본적으로 이벤트는 동기식으로 처리됩니다. 하지만 경우에 따라 비동기적으로 처리해야 할 때가 있습니다.

비동기 처리를 활성화하려면 Executor를 가진 ApplicationEventMulticaster Bean으로 생성하여 설정에서 이를 활성화할 수 있습니다.

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

 

이 설정 후, 리스너는 비동기적으로 이벤트를 처리하게 됩니다. 이벤트, 발행자, 리스너의 구현 방식은 기존과 동일하게 유지됩니다.

 


어노테이션을 활용한 이벤트 처리


Spring 4.2부터는 리스너를 구현할 때 반드시 ApplicationListener 인터페이스를 상속받을 필요가 없어졌습니다. 대신, 메서드에 @EventListener 애너테이션을 추가해 리스너로 사용할 수 있습니다.

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

 

메서드 시그니처를 통해 어떤 이벤트를 처리할지 결정합니다. 기본적으로 동기식으로 동작하지만, @Async 애너테이션을 추가하면 비동기적으로 처리할 수 있습니다. 단, 애플리케이션에서 비동기 처리를 활성화해야 합니다.

 


 

제네릭 타입을 활용한 이벤트


Spring은 제네릭 타입을 활용해 이벤트와 리스너를 보다 유연하게 설계할 수 있습니다.

또한, ApplicationEvent를 상속하지 않고도 이벤트를 정의할 수 있습니다.

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

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

 

제네릭 리스너

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

 

조건부 이벤트 핸들링

@EventListener 애너테이션에 조건을 추가하여 특정 조건을 만족할 때만 이벤트를 처리할 수 있습니다.

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

 

여기서 #event.success는 SpEL(Spring Expression Language)을 활용한 표현식입니다.

success 필드가 true인 경우에만 메서드가 호출됩니다.

 

 

제네릭 발행자(Generic Publisher)

제네릭 이벤트 발행자는 일반적인 이벤트 발행 방식과 유사하지만, 타입 소거(Type Erasure)로 인해 제네릭 파라미터를 해결할 수 있도록 별도의 구현이 필요합니다. 예를 들어, 특정 타입의 제네릭 이벤트를 발행하려면 다음과 같이 구현할 수 있습니다.

public class GenericStringSpringEvent extends GenericSpringEvent<String> {
    public GenericStringSpringEvent(String what, boolean success) {
        super(what, success);
    }
}

 

대안적인 이벤트 발행 방법

Spring에서는 이벤트 발행을 더 유연하게 처리하기 위한 대안적인 방법을 제공합니다. 이를 활용하면 메서드에서 이벤트 객체를 반환하여 이벤트를 발행할 수 있습니다.

@EventListener를 통한 새로운 이벤트 발행

@EventListener로 주석이 달린 메서드가 null이 아닌 값을 반환하면, Spring은 반환된 객체를 새로운 이벤트로 간주하고 다시 발행합니다.

@Component
public class AnnotationDrivenEventPublisher {
    @EventListener
    public GenericSpringEvent<String> handleAndPublish(String input) {
        System.out.println("Handling input and publishing a new event.");
        return new GenericSpringEvent<>(input, true);
    }
}

위의 예제에서 메서드가 GenericSpringEvent 객체를 반환하면, Spring은 해당 객체를 새로운 이벤트로 발행합니다.

 


 

Transaction Bound Events


Spring 4.2부터, 프레임워크는 새로운 @TransactionalEventListener 애너테이션을 제공합니다.
이는 @EventListener의 확장으로, 이벤트 리스너를 트랜잭션의 특정 단계에 바인딩할 수 있게 해줍니다.

 

바인딩은 다음 트랜잭션 단계에 가능합니다:

  1. AFTER_COMMIT (기본값): 트랜잭션이 성공적으로 완료된 후 이벤트를 발생시킵니다.
  2. AFTER_ROLLBACK: 트랜잭션이 롤백된 경우 이벤트를 발생시킵니다.
  3. AFTER_COMPLETION: 트랜잭션이 완료된 경우 이벤트를 발생시킵니다.
    (AFTER_COMMIT 및 AFTER_ROLLBACK의 별칭)
  4. BEFORE_COMMIT: 트랜잭션 커밋 직전에 이벤트를 발생시킵니다.
@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

 

https://newwisdom.tistory.com/75

 

ApplicationEventPublisher 적용과 그 안에서의 삽질

ApplicationEventPublisher 이벤트 핸들러는 이벤트 생성 주체가 발생한 이벤트에 반응하고, 이벤트 핸들러는 생성 주체가 발행한 이벤트를 전달받아 이벤트에 담긴 정보(데이터)를 기반으로 해당 기능

newwisdom.tistory.com

 

'Spring' 카테고리의 다른 글

[KAFKA] Spring Boot로 KAFKA 사용해보기  (0) 2025.04.02
[해결 방안] Spring STOMP content-length 초과 에러 해결하기  (0) 2025.03.31
[해결 방안] Spring Gateway를 통해 Stomp를 설정했을때 헤더가 두개오는 문제 해결방안  (1) 2025.03.31
[Spring] MongoDB와 JPA Repository 충돌 해결  (1) 2025.02.09
Spring Events을 활용하여 결합도 낮추기  (5) 2024.12.23
'Spring' 카테고리의 다른 글
  • [해결 방안] Spring STOMP content-length 초과 에러 해결하기
  • [해결 방안] Spring Gateway를 통해 Stomp를 설정했을때 헤더가 두개오는 문제 해결방안
  • [Spring] MongoDB와 JPA Repository 충돌 해결
  • Spring Events을 활용하여 결합도 낮추기
절박한개발자
절박한개발자
깃허브 주소 : https://github.com/Kzerojun
  • 절박한개발자
    절박한개발
    절박한개발자
  • 전체
    오늘
    어제
    • 분류 전체보기 (99)
      • Server (5)
      • 프로젝트 (7)
      • Spring (7)
      • AI (1)
      • JPA (6)
      • JAVA (7)
      • Backend (3)
      • WEB (3)
      • 알고리즘-이론 (6)
      • 알고리즘-문제 (28)
      • CS (24)
        • 데이터베이스 (8)
        • Network (5)
        • OS (10)
        • LINUX (1)
      • 개발면접준비 (1)
      • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    2
    CPU
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
절박한개발자
[Spring] Spring Events 이란?
상단으로

티스토리툴바