본문 바로가기
프로그래밍/Spring

[Spring] Spring의 Event를 어떻게 사용하는지에 대해서 알아봅시다

by 사바라다 2021. 8. 11.

안녕하세요. 오늘은 Spring의 Event에 대해서 알아보는 시간을 가져보도록 하겠습니다.

오늘 알아볼 내용은 Event란 무엇인가 ? 그리고 Spring에서 구현하는 방법은 무엇인가 ? 에 대한 내요입니다.

Event ?

Spring은 내부에 Event라는 메커니즘을 가지고 있습니다. Event란 Spring의 Bean과 Bean 사이의 데이터를 전달하는 방법중 하나입니다.

일반적으로 데이터를 전달하는 방법은 DI를 통해서 이루어집니다. 아래의 코드를 보시면 A 클래스에서 B에 대한 의존성을 주입받는 것을 알 수 있습니다. 이렇게 A는 이제 B의 메서드를 호출하여 본인의 클래스에서 사용할 수 있게 됩니다.

@Service
public class A {

    private final B service;

    public A(B service) {
        this.service = service;
    }
}

Event는 이와는 다른 메커니즘입니다. A Bean에서 이벤트를 ApplicationContext로 넘겨주고 이를 Listener에서 받아서 처리합니다.

아래에서 어떻게 사용하는지 보도록 하겠습니다.

이벤트를 사용하는 방법

이벤트는 이벤트를 발생시키는 publisher와 이것을 받아들이는 listener가 있습니다. 그리고 Event에서 데이터를 담는 Event 모델이 있습니다.

Event 모델

아래는 Event 모델의 코드입니다. Event 모델은 정보를 담고 있습니다. 아래예제에서는 name과 age를 전달하기 위해 만들어졌다는 것을 알 수 있습니다. 그리고 특징으로는 ApplicationEvent를 상속받고 super로 생성자에서 객체를 전달하고 있습니다. 이 object는 일반적으로 publisher를 가지고 있는 객체를 사용합니다.

public class DomainEvent extends ApplicationEvent {

    private final String name;
    private final int age;

    public DomainEvent(Object object, String name, int age) {
        super(object);
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Event Publisher

아래 코드는 Event를 생성하는 Publisher 입니다. Event를 생성하기 위한 bean은 ApplicationEventPublisher입니다. 따라서 사용하기 위해서는 아래의 코드처럼 주입을 받고 publishEvent 메서드를 사용하면 됩니다. 그리고 event의 object 파라미터로 본인 객체인 this를 주며 인스턴스를 만든 후 publishEvent의 파라미터로 넘기면 Event가 생성됩니다.

@Slf4j
@Component
public class EventTestService {

    private final ApplicationEventPublisher applicationEventPublisher;

    public EventTestService(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishCustomEvent(final String message) {
        log.info("Publishing custom event. ");
        DomainEvent customSpringEvent = new DomainEvent(this,"karol", 15);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

Event Listener

그리고 이벤트를 받아들이는 Event Listener에 대해서 알아보도록 하겠습니다. Listener는 Publisher에서 발행된 Event를 사용하는 부분입니다. Listner를 만들어주기 위해서는 ApplicationListener<{Event_Name}>을 상속받아야합니다. 그리고 onApplicationEvent를 오버라이딩하여 Event를 소비할 수 있습니다.

@Slf4j
@Component
public class EventTestListener implements ApplicationListener<DomainEvent> {

    @Override
    public void onApplicationEvent(DomainEvent event) {
        log.info("name = " + event.getName() + ", age = " + event.getAge());
        log.info("Handling context started event.");
    }
}

기본적인 테스트 결과

아래는 기본적인 테스트 결과입니다. 알아두셔야할 점은 Publisher와 Linstener가 기본적으로는 같은 thread에서 돌고 있다는 점입니다. Async로 돌려주기 위해서는 별도의 설정이 필요합니다. ApplicationEventMulticaster를 이용하면 된다는 것을 알아주시면 됩니다.

2021-08-08 23:26:55.210  INFO 67247 --- [    Test worker] c.p.a.api.game.service.EventTestService  : Publishing custom event. 
2021-08-08 23:26:55.216  INFO 67247 --- [    Test worker] c.p.a.a.game.service.EventTestListener   : name = karol, age = 15
2021-08-08 23:26:55.216  INFO 67247 --- [    Test worker] c.p.a.a.game.service.EventTestListener   : Handling context started event.

다중 Event Listener 테스트 결과

아래는 Event Listner를 2개를 두었을때의 테스트 결과입니다. Event Listener가 차례대로 실행되는 것을 알 수 있었습니다. @Order를 통하여 순서를 정할 수 있습니다.

2021-08-08 23:32:19.150  INFO 67833 --- [    Test worker] c.p.a.a.game.service.EventTestListener   : name = karol, age = 15
2021-08-08 23:32:19.150  INFO 67833 --- [    Test worker] c.p.a.a.game.service.EventTestListener   : Handling context started event.
2021-08-08 23:32:19.150  INFO 67833 --- [    Test worker] c.p.a.a.game.service.EventTestListener2  : name = karol, age = 15
2021-08-08 23:32:19.151  INFO 67833 --- [    Test worker] c.p.a.a.game.service.EventTestListener2  : Handling context started event.

Annotation 기반 Event Listener

위의 사용방식은 상속을 사용하기 때문에 다중상속이 불가능한 문제 등의 제약조건을 가질 수 있습니다. 따라서 Spring에서는 어노테이션 기반의 Event를 지원합니다. 아래에서 코드와 함께 예제를 보도록 하겠습니다.

Listener

Linstener를 보시면 @EventListener를 이용하고 파라미터로 해당 Event를 받으면 됩니다.

@Slf4j
@Component
public class EventTestListener {

    @EventListener
    public void handleContextStart(DomainEvent cse) {
        log.info("name = " + cse.getName() + ", age = " + cse.getAge());
        log.info("Handling context started event.");
    }
}

Event

이렇게 했을 때 추가적으로 더 좋은 점은 Event 모델도 extends ApplicationEvent를 뺄 수 있다는 점입니다. 아래 코드를 보시면 상속을 빼기 때문에 super() 부분도 빠진것을 확인하실 수 있습니다. 따라서 좀 더 전달하고자 하는 정보에 집중할 수 있게 되는 것입니다.

public class DomainEvent {

    private final String name;

    private final int age;

    public DomainEvent(String name, int age) {
        super(object);
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

마무리

오늘은 이렇게 Event를 사용하는 방법에 대해서 알아보는 시간을 가져보았습니다.

Event를 잘 이용하면 Domain Service에 불필요한 service 또는 infrastructure에 대한 의존성을 줄여서 해당 서비스를 더 잘 고립시킬 수 있습니다.

감사합니다.

참조

baeldung_spring-events

spring_context-functionality-events

댓글