본문 바로가기
Java, Kotlin, Spring/Spring, Spring Boot

Spring - ApplicationEventPublisher

by Wordbe 2021. 1. 12.
728x90

ApplicationContext 은 여러가지 인터페이스를 상속받는다.

그 중 ApplicationEventPublisher 를 알아보자.

이벤트 프로그래밍에 필요한 인터페이스를 제공한다.

 

ApplicationEventPublisher

옵저버 패턴 구현체로, 이벤트 기반 프로그래밍을 할 때 유용하다.

간단한 예제는 아래와 같다.

1) 이벤트를 만든다.

2) 이벤트를 Listener 에 등록시킨다.

3) AppRunner 에서 이벤트를 발생시키고, 위에서 등록한 이벤트 리스너가 이벤트를 작동시키도록 한다.

 

1) 이벤트 생성

public class BlueEvent extends ApplicationEvent {

    private int data;

    public int getData() {
        return data;
    }

    public BlueEvent(Object source, int data) {
        super(source);
        this.data = data;
    }
}

 

2) 이벤트 리스너 등록

@Component
public class BlueEventHandler implements ApplicationListener<BlueEvent> {

    @Override
    public void onApplicationEvent(BlueEvent blueEvent) {
        System.out.println("이벤트 받았다. data: " + blueEvent.getData());
    }
}

 

3) 이벤트 실행

AppRunner 를 작성하여, ApplicationEventPublisher 를 주입시킨 후 publishEvent() 메소드를 실행해보자.

BlueEvent 라는 객체에 파라미터를 담아 전달해주어보자.

@Component
public class AppRunner implements ApplicationRunner {

    // ApplicationContext 를 받아와도 된다.
    @Autowired
    ApplicationEventPublisher publisherEvent;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        publisherEvent.publishEvent(new BlueEvent(this, 100));
    }
}

 

 

 


 

이렇게 사용하길 권장합니다.

이 코드를 스프링 코드 없이 애노테이션만 달고 만들어보자. 즉, 비침투성을 만족하는 코드를 만들어보자.

비침투성 (Non-Invasive) = (Transparent)

  • 스프링 코드가 내 코드에 들어오지 않게 작성하는 것이다. POJO 기반 프로그래밍이다.

POJO : Plain Old Java Object, 오래된 방식의 자바 오브젝트

  • 특별한 제한에 종속되지 않고, 클래스패스(classpath)를 필요로 하지 않는 자바 객체를 의미한다. 상속, 인터페이스 구현, 어노테이션 사용을 금지한다.
  • 테스트가 더 편하고, 코드를 유지보수하기 쉬워진다.
public class BlueEvent {

    private int data;

    private Object source;

    public int getData() {
        return data;
    }

    public Object getSource() {
        return source;
    }

    public BlueEvent(Object source, int data) {
        this.source = source;
        this.data = data;
    }
}

이벤트는 더이상 ApplicationEvent 를 상속받지 않는다. 독립적인 코드다.

이렇게 할 수 있는 이유는 스프링 4.2버전 이후로 위 이벤트 클래스를 상속받지 않아도 이벤트가 등록이 되기 때문이다.

@Component
public class BlueEventHandler {

    @EventListener
    public void handle(BlueEvent blueEvent) {
        System.out.println("이벤트 받았다. data: " + blueEvent.getData());
    }
}

핸들러 역시 더이상 ApplicationListener를 구현하지 않는다. 핸들러의 경우는 빈으로 등록을 해주어야 ApplicationContext에서 publishEvent로 불러와서 실행이 가능하므로 메소드위해 @EventListener 어노테이션을 붙여주자.

코드가 훨씬 깔끔해졌고, 잘 동작한다.

 


 

순서 정하기

조금 더 응용해보자.

다른 이벤트 핸들러를 추가하면, publishEvent 호출 시 등록된 모든 이벤트 핸들러가 동작한다.

순서는 정해진 순서대로 발생하지만, @Order 어노테이션으로 순서를 정할수도 있다.

@Component
public class BlueEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE // Blue 먼저
    public void handle(BlueEvent blueEvent) {
        System.out.println(Thread.currentThread().toString()); // 현재 스레드 확인
        System.out.println("BlueEventHandler " + blueEvent.getData());
    }
}
@Component
public class GreenEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE + 1) // Green 나중
    public void handle(BlueEvent blueEvent) {
        System.out.println(Thread.currentThread().toString()); // 현재 스레드 확인
        System.out.println("GreenEventHandler " + blueEvent.getData());
    }
}

 

비동기

위 이벤트들은 기본적으로 동기적(synchronous) 으로 발생한다.

이벤트들이 비동기로 동작하게 하려면 @Async 어노테이션을 붙이면 된다. 이렇게 하려면 프로젝트 최상위 클래스인 메인 클래스 위에도 @EnableAsync 어노테이션을 붙이면 된다.


 

여러 이벤트들

package me.wordbe.springgoahead;

import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class BlueEventHandler {

    @EventListener
    @Async
    public void handle(BlueEvent blueEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("이벤트 받았다. data: " + blueEvent.getData());
    }

    @EventListener
    @Async
    public void handle(ContextRefreshedEvent blueEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("컨텍스트 리프레쉬 이벤트");
    }

    @EventListener
    @Async
    public void handle(ContextClosedEvent blueEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("컨테스트 클로즈드 이벤트");
    }
}
  • ContextRefreshedEvent 는 ApplicationContext 를 초기화하거나, 리프레시 했을 때 발생한다.
  • ContextStartedEvent 는 ApplicationContext 를 start() 해서 라이프사이클 빈들이 시작 신호를 받은 시점에 발생한다.
  • ContextStoppedEvent : ApplicationContext 를 stop() 해서 라이프사이클 빈들이 정지 신호를 받은 시점에 발생한다.
  • ContextClosedEvent : ApplicationContext 를 close() 해서 싱글톤 빈이 소멸되는 시점에 발생한다. (실행 중인 스프링을 중지하면 발생한다.)
  • RequestHandledEvent : HTTP 요청을 처리했을 때 발생한다.

 

728x90

'Java, Kotlin, Spring > Spring, Spring Boot' 카테고리의 다른 글

Spring - Validation  (4) 2021.01.12
Spring - ResourceLoader  (64) 2021.01.12
Spring - Messagesource  (2) 2021.01.12
Spring - 프로파일, 프로퍼티  (4) 2021.01.11
Spring IoC - ComponentScan, Bean scope  (2) 2021.01.10

댓글