본문 바로가기
Spring

[Spring] ApplicationEventPublisher

by dbjh 2020. 5. 15.
반응형

이번 글에서는 옵저버 패턴의 구현체로 이벤트 기반의 프로그래밍을 할때 유용한인터페이스인  ApplicationEventPublisher에 대해 알아보도록 하자.

여기서 말하는 옵저버 패턴을 간략하게 실생활에 빗대어 표현하면, 스마트폰에 날씨 어플리케이션이 저장되어 있다고 가정하자. 이 어플리케이션은 기상청의 데이터를 수시로 읽어서 스마트폰을 사용하는 사용자에게 푸쉬알람, 진동등을 통해서 어떠한 상황변화를 전달할 수 있다.

프로그래밍에서 이 상황을 설명한다면, 상태를 가진 객체가 기상(날씨)가 되는것이고 기상(날씨)를 관찰하는 기상청의 컴퓨터 또는 기상청의 직원을 말할 수 있다. 여기서 Observer(보는사람, 목격자)기상청의 컴퓨터 또는 직원을 말한다.
어떠한 상태를 가지는 객체 AA의 상태를 관찰하는 하나의 객체 B 또는 A의 상태를 관찰하는 여러개의 객체 B, C, D, F .... 의 구조를 가진 패턴을 옵저버 패턴이라고 한다.


스프링에서 이벤트기반 프로그래밍을 어떤식으로 처리 하는지 코드를 보면서 알아보도록하자.


1. 스프링에서 이벤트 프로그래밍을 하는 방식

MyEvent.java
public class MyEvent extends ApplicationEvent {

    private int data;

    public MyEvent(Object source) {
        super(source);
    }

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

    public int getData() {
        return data;
    }
}

 

AppRunner.java
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher publisherEvent;

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

 

MyEventHandler.java
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        System.out.println("receive Event !! data = "+ myEvent.getData());
    }
}

 

스프링 4.2 버전 이전에는 ApplicationListener 인터페이스를 구현하여 이벤트 프로그래밍을 처리했다.
위의 3개의 클래스를 작성하고 실행하면, Bean으로 등록된 AppRunner가 실행이 되면서 publishEvent() 메소드에 인자로 넣어준 MyEvent를 발생 시킨다. 그러면 발생시킨 이벤트를 등록되어 있던 Bean인 MyEventHandler가 받아서 처리를한다. 

스프링 4.2 이후 부터는 위와 같은 상속 및 구현하는 제약사항들이 사라졌다. 그래서 위의 코드들은 아래와 같이 변경하여 사용한다.

MyEvent.java
package com.example.demo;

public class MyEvent {

    private int data;

    private Object source;

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

    public int getData() {
        return data;
    }

    public Object getSource() {
        return source;
    }
}

위와 같은 코드가 바로 스프링프레임워크가 추구하는 철학이다. 바로 비침투성을 의미하는데, 위의 코드에는 스프링 프레임워크 관련 코드가 포함되어 있지않다. 이게 바로 POJO(Plain Old Java Object) 이며, 테스트 및 유지보수하기 쉬워진다.

MyEventHandler.java
@Component
public class MyEventHandler {

    @EventListener
    public void occurEvent(MyEvent myEvent) {
        System.out.println("receive Event !! data = "+ myEvent.getData());
    }
}

위의 코드와 같이 이벤트를 처리하는 메소드위에 @EventListener를 선언하여 이벤트로 등록해준다.

위처럼 이벤트 클래스에는 어떠한 스프링코드도 들어가지않고 이벤트 핸들러 어노테이션만 포함된 코드를 추천한다.


2. 다중 이벤트 핸들러

스프링에서는 위의 글에서 MyEventHandler 클래스 처럼 또 다른 이벤트 핸들러를 등록하여 사용할 수 있다.

아래와 같이 MyEventHandler 클래스의 출력 부분을 수정하고, 또 다른 이벤트 핸들러 클래스를 작성하도록 하자.

MyEventHandler.java
@Component
public class MyEventHandler {

    @EventListener
    public void occurEvent(MyEvent myEvent) {
        System.out.println("My Event, data = "+ myEvent.getData());
    }
}

 

YourEventHandler.java
@Component
public class YourEventHandler {

    @EventListener
    public void occurEvent(MyEvent myEvent) {
        System.out.println("Your Event, data = " + myEvent.getData());
    }
}

그 상태에서 다시 실행시켜보면 순차적으로 실행이된다. 정확히 알 수는 없지만 디렉토리 아래의 클래스 명 순서대로 실행이 되는것으로 보인다. 여기서는 MyEventHandler -> YourEventHandler 순서로 실행이 된다. 서로 다른 Thread에서 두 이벤트를 핸들링 하는것이 아니라 두 이벤트 모두 Main Thread에서 다뤄진다.

이벤트 실행 순서


순서를 임의로 정해주고 싶다면, 또 다른 어노테이션을 추가해주면된다. 코드를 살펴보도록하자.

MyEventHandler.java
@Component
public class MyEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE + 2)
    public void occurEvent(MyEvent myEvent) {
        System.out.println("My Event, data = "+ myEvent.getData());
    }
}

 

YourEventHandler.java
@Component
public class YourEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void occurEvent(MyEvent myEvent) {
        System.out.println("Your Event, data = " + myEvent.getData());
    }
}

위와같이 @EventListener 어노테이션이 선언된 메소드에 @Order 어노테이션 사용하여 순서를 정할 수 있다.

@Order 어노테이션 내부에 선언된 Ordered.HIGHEST_PRECEDENCE 의 뜻은 가장 먼저 실행하겠다는 표시이고, 다른 핸들러가 추가될 수도 있는 상황이기 때문에 두 이벤트 핸들러 모두 Ordered.HIGHEST_PRECEDENCE 를 선언하고, 위에서 실행되었던 순서를 바꾸기 위해 MyEventHandler에는 2를 더해준 후에 실행하도록 하자.

뒤바뀐 이벤트 실행 순서

 

추가적으로, 위의 이벤트 핸들러를 비동기 적으로 사용하고 싶다면 이벤트 핸들러에 @Async 어노테이션을 선언하여 사용할 수 있다. 이런 경우 서로 다른 스레드풀에 따로 돌기 때문에 @Order 어노테이션이 더이상 의미가 없어진다.
이벤트핸들러에 @Async 어노테이션만 추가해준다고해서 비동기적으로 동작하는 것은 아니다. 아래와 같이 main 함수를 가지고 있는 클래스에 @EnableAsnc 어노테이션을 추가하자. 그리고, Thread가 실질적으로 다른지도 실행하여 확인해보도록하자.

MyEventHandler.java
@Component
public class MyEventHandler {

    @EventListener
    @Async
    public void occurEvent(MyEvent myEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("My Event, data = "+ myEvent.getData());
    }
}

 

YourEventHandler.java
@Component
public class YourEventHandler {

    @EventListener
    @Async
    public void occurEvent(MyEvent myEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("Your Event, data = " + myEvent.getData());
    }
}

 

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

별도의 스레드에 동작한 결과

위와 같이 스프링에서 이벤트 프로그래밍을 하는 방식을 알아보았는데, 마지막으로 스프링이 기본적으로 제공하는 ApplicationContext 관련된 이벤트를 처리하는 핸들러를 살펴보도록하자.

MyEventHandler.java
@Component
public class MyEventHandler {

    @EventListener
    @Async
    public void occurEvent(MyEvent myEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("My Event, data = "+ myEvent.getData());
    }

    @EventListener
    @Async
    public void occurEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextRefreshedEvent");
    }

    @EventListener
    @Async
    public void occurEvent(ContextClosedEvent contextClosedEvent) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("ContextClosedEvent");
    }
}

바로 위에서 작성한 이벤트들은 ApplicationContext에 관한 것들이라 직접 ApplicationContext 객체로 직접 호출하여 사용할 수 있다. SpringBoot는 위의 이벤트들을 확장하여 더 다양한 이벤트들을 제공한다. 위처럼 코드를 수정하고 실행해보면 ContextRefreshedEvent 발생하고 ApplicationContext가 종료될때 ContextClosedEvent 이벤트가 발생하는 것을 확인할 수 있다.

이번 글에서는 스프링의 이벤트 프로그래밍에 대해 알아보았고, ApplicationContextApplcationEventPublisher를 상속하여 그 기능을 사용할 수 있다는 것도 알아두도록 하자.

 

내용 출처

반응형

'Spring' 카테고리의 다른 글

[Spring] IntelliJ 에서 queryDSL 의 Q 도메인을 찾지 못할때  (0) 2020.08.23
[Spring] ResourceLoader  (0) 2020.05.17
[Spring] MessageSource  (0) 2020.01.18
[Spring] Environment - Property  (0) 2020.01.07
[Spring] Environment - Profile  (0) 2019.12.29

댓글