안녕하세요. 오늘은 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에 대한 의존성을 줄여서 해당 서비스를 더 잘 고립시킬 수 있습니다.
감사합니다.
참조
'language, framework, library > Spring' 카테고리의 다른 글
[Spring + JPA] jpa에서 Repository를 이용한 비관적락을 구현해봅시다. With MariaDB (0) | 2021.08.16 |
---|---|
[JPA] jpa에서 Repository를 이용한 낙관적락을 구현해봅시다. (1) | 2021.08.14 |
[JPA] 테이블의 한 컬럼 내에서 N개의 값을 가지는 방법 (0) | 2020.12.12 |
[Spring] Spring의 핵심기술 PSA - 개념과 원리 (3) | 2020.11.25 |
[Spring] Scheduler 어떤걸 사용해야 할까 ? - Spring Scheduler와 Spring Quartz (1) | 2020.09.15 |
댓글