[Spring] ApplicationEventPublisher

joyful·2021년 8월 27일
0

Java/Spring

목록 보기
17/28
post-thumbnail
post-custom-banner

1. 개요

  • ApplicationContext에 대한 상위 인터페이스 역할 수행
  • 이벤트 기반 프로그래밍에 유용한 인터페이스

    💡 이벤트 기반 프로그래망?

    Event 객체가 있고, Event 객체를 다루는 EventHandler 객체가 존재하는 것

  • 디자인 패턴 중 하나인 Observer Pattern의 구현체

    💡 Observer Pattern

    • 하나의 객체가 상태를 변경하면 모든 종속된 항목이 자동으로 알림을 받고 업데이트되도록 객체 간의 일대다 종속성을 정의하는 것
    • 여러 관찰자 개체가 이벤트를 볼 수 있도록 하는 게시/구독 패턴의 하위 집합
    • 개체가 다른 개체에 메시지를 알릴 수 있어야 하고 이러한 개체들이 밀접하게 결합되는 것을 원하지 않을 때 사용


2. 구조

✅ ApplicationEventPublisher

  • 이벤트 프로그래밍에 필요한 인터페이스 제공
  • 기본적으로 ApplicationContext 인터페이스에 상속되어 있음
    ApplicationContext 구현체에서 접근 가능
  • publishEvent 메소드 제공
    → 스프링 기반 어플리케이션에 이벤트 발생

✅ ApplicationEvent

  • 이벤트 객체를 생성하기 위해 상속받아야 하는 클래스
  • ApplicationEvent를 상속받아 사용자가 원하는 데이터를 입력받도록 구현

✅ ApplicationListener<E extends ApplicationEvent>

  • 이벤트를 리스닝하기 위해 구현해야 하는 인터페이스
  • 기본적으로 ApplicationListener를 구현하여 onApplicationEvent(E) 메소드 구현

    💡 onApplicationEvent(E)

    • Spring에서 제공하는 ApplicationEventPublisher에서 특정 이벤트를 publish하는 경우 자동으로 실행
    • 매개변수로 전달되는 event를 이용해 원하는 작업 처리 구현 가능
  • 제네릭으로 ApplicationEvent 상속받은 클래스를 지정하여 컴파일 타임에 형 안정성을 보장받을 수 있음


3. 구현 방법

✅ Spring 4.2 이전

1) 이벤트 생성

public class ExampleEvent extends ApplicationEvent {

    private int data;
    
    public ExampleEvent(Object source, int data) {
        super(source);
        this.data = data;
    }
    
    public int getData() {
        return data;
    }
}
  • event를 발생시킬 때 생성할 것이므로 Bean에 등록 되지 않아도 됨

2) 이벤트를 리스너에 등록

@Component
public class ExampleEventHandler implements ApplicationListener<ExampleEvent> {

    @Override
    public void onApplicationEvent(ExampleEvent event) {
        System.out.println("이벤트 전달 받음, data = " + event.getData());
    }
}
  • 매개변수로 전달되는 event의 message 출력
  • IoC 컨테이너에 의해 사용
    → Spring이 발생한 이벤트를 누구에게 전달해야하는지 알아야 하므로 빈으로 등록해야 함
    @Component 추가

3) 이벤트 실행

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher eventPublisher;
  //ApplicationContext ctx;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        eventPublisher.publishEvent(new ExampleEvent(this, 100));
    }
}
  • ApplicationEventPublisherApplicationContext에 의해 구현
    → IoC 컨테이너에 의해 제공되는 기능
  • 작동
    • publishEvent() 메소드 호출
      run() 메소드가 실행되어 이벤트 발생
    • 등록되어 있는 bean 중에서 ExampleEventHandler 클래스를 실행시켜 콘솔에 결과 출력
  • ApplicationContext 타입도 주입 가능

✅ Spring 4.2 이후

Spring 4.2부터는 POJO 구조로, Spring 코드 없이 이벤트 기반의 프로그래밍 가능

1) 이벤트 생성

public class ExampleEvent {

    private int data;
    
    public ExampleEvent(Object source, int data) {
        super(source);
        this.data = data;
    }
    
    public int getData() {
        return data;
    }
}
  • ApplicationEvent 상속 x

2) 이벤트를 리스너에 등록

@Component
public class ExampleEventHandler {

    @EventListener
    public void onApplicationEvent(ExampleEvent event) {
        System.out.println("이벤트 전달 받음, data = " + event.getData());
    }
}
  • ApplicationListener을 구현하는 대신 @EventListener 어노테이션 사용

3) 이벤트 실행

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher eventPublisher;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        eventPublisher.publishEvent(new ExampleEvent(this, 100));
    }
}
  • 변경 사항 x


4. 이벤트 수신 순서 지정

같은 이벤트를 다루는 이벤트 핸들러가 여러개인 경우 순차적으로 진행되며, 실행 순서는 알 수 없다.

@Order 이용

  • 핸들러의 순서가 필요한 경우 @Order 어노테이션을 사용하여 순서 지정
  • 옵션
    • HIGHEST_PRECEDENCE : 가장 높은 우선순위 (=Integer.MIN_VALUE)
    • LOWEST_PRECEDENCE : 가장 낮은 우선순위 (=Integer.MAX_VALUE)
@Component
public class OrderHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE + 1) //설정하는 값이 클수록 우선순위 ↓
    public void onApplicationEvent(ExampleEvent event) {
        System.out.println("이벤트를 전달 받음, data = " + event.getMessge());
    }
}


5. 이벤트 비동기 처리

  • 이벤트 핸들러는 기본적으로 동기적으로 실행
  • 비동기적으로 이벤트를 수신하려면 어노테이션을 지정해주면 됨

💡 동기와 비동기

  • 동기(Sync)
    • 어떠한 작업이 순차적으로 실행
    • 요청 시 결과 반환을 기다려야 함
  • 비동기(Async)
    • 어떠한 작업이 동시에 실행
    • 요청과 결과 반환이 동시에 일어나지 않음

✅ 방법

1) 이벤트 처리 메소드에 @Async 지정

@Component
public class ExampleEventHandler{
 
    @EventListener
    @Async
    public void onApplicationEvent(ExampleEvent event) {
        System.out.println("이벤트 전달 받음, data = " + event.getData());
    }

2) 프로젝트 최상위 클래스인 main 클래스에 @EnableAsync 지정

@SpringBootApplication
@EnableAsync
public class ExampleApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}


6. Spring 기본 제공 이벤트

✅ 종류

이벤트설명
ContextRefreshedEventApplicationContext를 초기화하거나 리프레시 했을 때 발생
ContextStartedEventApplicationContext를 start()하여 라이프 사이클 빈들이
시작 신호를 받은 시점에 발생
ContextStoppedEventApplicationContext를 stop()하여 라이프 사이클 빈들이
정지 신호를 받은 시점에 발생
ContextClosedEventApplicationContext를 close()하여 싱글톤 빈이 소멸되는 시점에 발생
(실행 중인 스프링을 중지하면 발생)
RequestHandledEventHTTP 요청을 처리했을 때 발생

✅ 사용 예시

@Component
public class ExampleEventHandler {

    @EventListener
    @Async
    public void handle(ContextRefreshedEvent event){
        System.out.println("Refresh");
    }

    @EventListener
    @Async
    public void handle(ContextStartedEvent event){
        System.out.println("Started");
    }

    @EventListener
    @Async
    public void handle(ContextStoppedEvent event){
        System.out.println("Stopped");
    }

    @EventListener
    @Async
    public void handle(ContextClosedEvent event){
        System.out.println("Closed");
    }
}
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationContext ctx;
  //ApplicationEventPublisher eventPublisher;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        ctx.publishEvent(new ExampleEvent(this, 100));
        ((ConfigurableApplicationContext)ctx).start();
        ((ConfigurableApplicationContext)ctx).stop();
        ((ConfigurableApplicationContext)ctx).close();
    }



📖 참고

profile
기쁘게 코딩하고 싶은 백엔드 개발자
post-custom-banner

0개의 댓글