Spring Boot의 EventHandler #2

2021. 2. 7. 17:39카테고리 없음

이전 글 > 2021/02/02 - [분류 전체보기] - Spring Boot의 EventHandler #1

 

Spring Boot의 EventHandler #1

Spring Framework를 기반으로 애플리케이션을 구성할 때, 보통 웹 기반으로 합니다. @Controller나 @RestController annotation을 사용하여 외부의 이벤트(Http Request)에 의해서 동작합니다. 때로는 외부 요청..

matriluna.tistory.com

이전 글에서는 Spring에서 제공하는 ApplicationStartedEvent를 이용하여 애플리케이션이 실행된 직후에 원하는 동작을 수행하였습니다. 개발자가 자신이 만든 이벤트를 발행해 보도록 하겠습니다.

1. 개발자가 작성한 Event 발행

먼저 간단한 이벤트 클래스를 만들었습니다.

  • TestEvent
public class TestEvent {
    private String message;

    public TestEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

 

message라는 문자열 속성을 갖는 아주 간단한 POJO 클래스입니다. 애플리케이션 내부에서 이 클래스의 이벤트가 발생되면 이벤트에 해당하는 행동을 하고 싶습니다. 이는 마치 GUI 애플리케이션을 작성할 때 사용자에 의해서 버튼이 눌려졌다 또는 이전 글에서 예시를 보았던 것처럼 애플리케이션이 시작되었다라는 이벤트를 수신하는 것과 같습니다.

  • TestEventListener

@Component
public class TestEventListener {
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    @EventListener(TestEvent.class)
    public void handleTestEvent() {
        log.info("[handleTestEvent]");
    }
}

 

코드에서 보는 것처럼 ApplicationStartedEvent를 수신 하는 것과 매우 비슷하게 코드를 작성하면 됩니다. 이제 이벤트를 발행해 보겠습니다.

  • TestPublisher

@Component
public class TestPublisher {
    public final ApplicationEventPublisher eventPublisher;
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    public TestPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @EventListener(ApplicationStartedEvent.class)
    public void publish() {
        log.info("[publish]");
        TestEvent event = new TestEvent("Event_01");
        eventPublisher.publishEvent(event);
    }
}

ApplicationEventPublisher라는 Spring에서 제공해 주는 Bean을 주입 받는 Component를 하나 생성하였습니다. @EventListener(ApplicationStartedEvent.class)에 의해서 애플리케이션이 실행되자마자 TestEvent를 생성하여 발행하였습니다. 예상대로라면 [publish] 로그가 출력되고 바로 이어서 [handleTestEvent] 로그가 출력되는 것입니다.

  • 테스트 결과

INFO 6925 --- [           main] c.b.e.EventHandlerApplication            : Started EventHandlerApplication in 0.805 seconds (JVM running for 1.383)
INFO 6925 --- [           main] com.blog.eventhandler.TestPublisher      : [publish]
INFO 6925 --- [           main] com.blog.eventhandler.TestEventListener  : [handleTestEvent]

 

원하는 결과가 출력되었습니다.

2. 매개변수로 이벤트 클래스 전달

TestEventListener의 handleTestEvent 메소드는 매개변수가 없어서 TestEvent 객체의 내용을 알 수 없습니다. 내용을 알 수 있도록 추가해 보겠습니다. TestEventListener에 새로운 EventListener를 추가해 보겠습니다.

  • TestEventListener

@Component
public class TestEventListener {
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    @EventListener(TestEvent.class)
    public void handleTestEvent() {
        log.info("[handleTestEvent]");
    }

    @EventListener
    public void onTestEvent(TestEvent event) {
        log.info("[onTestEvent] message: {}", event.getMessage());
    }
}

 

onTestEvent라는 메소드를 추가하여 TestEvent를 매개변수로 받기로 하였습니다. 또 한 가지 주목해야 할 점은 @EventListener annotation에 어떤 이벤트인지를 명시하지 않았다는 점 입니다. [onTestEvent] message: Event_01이 출력되기를 바라면서 테스트를 다시 해 보겠습니다.

 

INFO 6947 --- [           main] c.b.e.EventHandlerApplication            : Started EventHandlerApplication in 0.833 seconds (JVM running for 1.371)
INFO 6947 --- [           main] com.blog.eventhandler.TestPublisher      : [publish]
INFO 6947 --- [           main] com.blog.eventhandler.TestEventListener  : [handleTestEvent]
INFO 6947 --- [           main] com.blog.eventhandler.TestEventListener  : [onTestEvent] message: Event_01

 

원하는 결과가 나왔습니다. 그런데 handleTestEvent 메소드와 onTestEvent가 모두 호출되었습니다. 발행 / 구독 패턴 형태로 이루어져 있다는 것을 추측할 수 있습니다. (또는 Observer 패턴)

3. ApplicationEventPublisher의 작동 시점

@EventHandler annotation에 이벤트 클래스를 명시하지 않았기 때문에 onTestEvent 메소드가 발행되는 모든 이벤트에 의해서 호출 되었을 수도 있습니다. ExampleEvent 클래스를 추가합니다.

  • ExampleEvent

public class ExampleEvent {
    private final String message;

    public ExampleEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "ExampleEvent{" +
                "message='" + message + '\'' +
                '}';
    }
}

 

클래스 이름만 다르지 내용은 TestEvent와 완전히 동일하게 만들었습니다. TestEventListener에 @EventHandler를 추가합니다.

  • TestEventListener

@Component
public class TestEventListener {
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    @EventListener(TestEvent.class)
    public void handleTestEvent() {
        log.info("[handleTestEvent]");
    }

    @EventListener
    public void onTestEvent(TestEvent event) {
        log.info("[onTestEvent] message: {}", event.getMessage());
    }

    @EventListener
    public void onExampleEvent(ExampleEvent event) {
        log.info("[onExampleEvent] {}", event);
    }
}

 

이벤트를 발행해야 합니다. TestPublisher 클래스를 수정합니다. 클래스가 생성될 때, ExampleEvent가 수행 되도록 해 보겠습니다.

  • TestPublisher

@Component
public class TestPublisher {
    public final ApplicationEventPublisher eventPublisher;
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    public TestPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
        eventPublisher.publishEvent(new ExampleEvent("Event_02"));
    }

    @EventListener(ApplicationStartedEvent.class)
    public void publish() {
        log.info("[publish]");
        TestEvent event = new TestEvent("Event_01");
        eventPublisher.publishEvent(event);
    }
}

 

TestPublisher의 생성자에서 이벤트를 발행했습니다.

 

INFO 6999 --- [           main] c.b.e.EventHandlerApplication            : Starting EventHandlerApplication using Java 11.0.10 on gimmingyuui-iMac.local with PID 6999 (/Users/minkyu/IdeaProjects/event-handler/target/classes started by minkyu in /Users/minkyu/IdeaProjects/event-handler)
INFO 6999 --- [           main] c.b.e.EventHandlerApplication            : No active profile set, falling back to default profiles: default
INFO 6999 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
INFO 6999 --- [           main] c.b.e.EventHandlerApplication            : Started EventHandlerApplication in 0.83 seconds (JVM running for 1.415)
INFO 6999 --- [           main] com.blog.eventhandler.TestPublisher      : [publish]
INFO 6999 --- [           main] com.blog.eventhandler.TestEventListener  : [handleTestEvent]
INFO 6999 --- [           main] com.blog.eventhandler.TestEventListener  : [onTestEvent] message: Event_01

 

로그를 아무리 찾아봐도 Event_02가 출력되지 않았습니다. Event_01과 같은 메소드에서 Event_02를 발행해 보겠습니다.

  • TestPublisher

@Component
public class TestPublisher {
    public final ApplicationEventPublisher eventPublisher;
    public final Logger log = LoggerFactory.getLogger(this.getClass());

    public TestPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @EventListener(ApplicationStartedEvent.class)
    public void publish() {
        log.info("[publish]");
        TestEvent event = new TestEvent("Event_01");
        eventPublisher.publishEvent(event);
        eventPublisher.publishEvent(new ExampleEvent("Event_02"));
    }
}
INFO 7021 --- [           main] c.b.e.EventHandlerApplication            : Started EventHandlerApplication in 0.826 seconds (JVM running for 1.377)
INFO 7021 --- [           main] com.blog.eventhandler.TestPublisher      : [publish]
INFO 7021 --- [           main] com.blog.eventhandler.TestEventListener  : [handleTestEvent]
INFO 7021 --- [           main] com.blog.eventhandler.TestEventListener  : [onTestEvent] message: Event_01
INFO 7021 --- [           main] com.blog.eventhandler.TestEventListener  : [onExampleEvent] ExampleEvent{message='Event_02'}

원하는 결과가 출력되었습니다. ApplicationEventPublisher는 Spring 애플리케이션이 완전히 실행된 뒤에 작동한다는 것을 알 수 있습니다.