2023.05.07 - [공부 기록] - [SPRING] 스프링이 Event를 다루는 방법 - EventListener
** spring 5.3 버전을 참조했습니다.
다음으로는 TransactionalEventListener 를 살펴보자, 이벤트 리스너에 Transaction을 처리하기 위한 보조 코드가 추가되었을 뿐이다.
때문에 패키지도 context 가 아닌 transaction 쪽이다.
이 중 자세히 볼만한 것은 TransactionalApplicationListenerMethodAdaptor와 TransactionalApplicationListener이다.
우리는 이전 게시글에서 ApplicationListenerMethodAdapter 가 실제 이벤트를 받아 처리해주는것을 보았다.
이번에도 마찬가지로 TransactionalApplicationListenerMethodAdaptor를 확인하면 될것 같은 느낌이 든다.
TransactionalApplicationListenerMethodAdaptor를 열어보니 ApplicationListenerMethodAdapter를 확장하고 TransactionalApplicationListener를 구현하고 있다. 잘 찾아온 것 같다.
public TransactionalApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
super(beanName, targetClass, method);
TransactionalEventListener ann =
AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);
if (ann == null) {
throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method);
}
this.annotation = ann;
this.transactionPhase = ann.phase();
}
생성자는 위와 같으며 이는 EventListenerFactory를 구현한 TransactionalEventListenerFactory의 createApplicationListenr 메서드에서 호출하고 있다.
이와 같이 이전 게시글에서 설명한 내용들은 바로 넘어가고 바로 TransactionalApplicationListenerMethodAdaptor를 확인해보겠다.
ApplicationListenerMethodAdapter를 확장하고 있기 때문에 코드가 그리 길지 않다.
public class TransactionalApplicationListenerMethodAdapter extends ApplicationListenerMethodAdapter
implements TransactionalApplicationListener<ApplicationEvent> {
private final TransactionalEventListener annotation;
private final TransactionPhase transactionPhase;
private final List<SynchronizationCallback> callbacks = new CopyOnWriteArrayList<>();
public TransactionalApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
super(beanName, targetClass, method);
TransactionalEventListener ann =
AnnotatedElementUtils.findMergedAnnotation(method, TransactionalEventListener.class);
if (ann == null) {
throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method);
}
this.annotation = ann;
this.transactionPhase = ann.phase();
}
@Override
public TransactionPhase getTransactionPhase() {
return this.transactionPhase;
}
@Override
public void addCallback(SynchronizationCallback callback) {
Assert.notNull(callback, "SynchronizationCallback must not be null");
this.callbacks.add(callback);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive() &&
TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionalApplicationListenerSynchronization<>(event, this, this.callbacks));
}
else if (this.annotation.fallbackExecution()) {
if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
}
processEvent(event);
}
else {
// No transactional event execution at all
if (logger.isDebugEnabled()) {
logger.debug("No transaction is active - skipping " + event);
}
}
}
}
우리가 주목할 부분은 onApplicationEvent이다. 기존의 EventListenrAdaptor처럼 설정 값을 통한 상태를 확인하고 그에 맞는 이벤트 진행 방식을 설정한다.
먼저 트랜잭션 여부를 확인하고 콜백을 설정한 객체를 등록한다. 콜백을 설정해서 등록한 객체는 아래와 같다.
class TransactionalApplicationListenerSynchronization<E extends ApplicationEvent>
implements TransactionSynchronization {
private final E event;
private final TransactionalApplicationListener<E> listener;
private final List<TransactionalApplicationListener.SynchronizationCallback> callbacks;
public TransactionalApplicationListenerSynchronization(E event, TransactionalApplicationListener<E> listener,
List<TransactionalApplicationListener.SynchronizationCallback> callbacks) {
this.event = event;
this.listener = listener;
this.callbacks = callbacks;
}
@Override
public int getOrder() {
return this.listener.getOrder();
}
@Override
public void beforeCommit(boolean readOnly) {
if (this.listener.getTransactionPhase() == TransactionPhase.BEFORE_COMMIT) {
processEventWithCallbacks();
}
}
@Override
public void afterCompletion(int status) {
TransactionPhase phase = this.listener.getTransactionPhase();
if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
processEventWithCallbacks();
}
else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
processEventWithCallbacks();
}
else if (phase == TransactionPhase.AFTER_COMPLETION) {
processEventWithCallbacks();
}
}
private void processEventWithCallbacks() {
this.callbacks.forEach(callback -> callback.preProcessEvent(this.event));
try {
this.listener.processEvent(this.event);
}
catch (RuntimeException | Error ex) {
this.callbacks.forEach(callback -> callback.postProcessEvent(this.event, ex));
throw ex;
}
this.callbacks.forEach(callback -> callback.postProcessEvent(this.event, null));
}
}
이벤트를 감싸고 미리 설정한 TransactionPhase에 따라 이벤트를 어떻게 처리할지를 관리한다.
참고로 이때의 콜백은 이렇게 생겼다.
interface SynchronizationCallback {
default void preProcessEvent(ApplicationEvent event) {
}
default void postProcessEvent(ApplicationEvent event, @Nullable Throwable ex) {
}
}
그 다음은 fallbackExecution 값에 따라 이벤트를 수행할지 여부를 처리한다.
이때 fallbackExecution의 설명은 "실행 중인 트랜잭션이 없는 경우 이벤트를 처리할지 여부입니다."이다.
때문에 첫번째 분기에서 트랜잭션이 존재하지 않고 이 설정값이 true이면 트랜잭션 없이도 이벤트를 수행한다.
설명을 보아하니 AFTER_ROLLBACK 페이즈에서 이 값을 true로 설정하면 트랜잭션이 없는 상태로 재시도나 예외 처리에 활용할 수 있을 것 같다.
else if (this.annotation.fallbackExecution()) {
if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
}
processEvent(event);
}
**
TransactionPhase.AFTER_COMMIT : 기본 설정으로 트랜잭션이 커밋 되었을 때 이벤트 실행
TransactionPhase.ROLLBACK : 트랜잭션이 rollback 되었을 때 이벤트 실행
TransactionPhase.AFTER_COMPLETION : 트랜잭션이 커밋 또는 롤백 되었을 때 이벤트 실행
TransactionPhase.BEFORE_COMMIT: 트랜잭션이 커밋 되기 전에 이벤트 실행
지금까지 스프링에서 이벤트를 다루는 방식에 대해 알아보았다.
어노테이션을 붙이고 활용하는 법이야 많으니 언제 만들어지고 어떻게 동작하는지, 어떤 구현체가 이를 담당하는지가 궁금해서 시작한 포스팅이다.
저번 게시글에도 추가했지만 설명이 잘 된 블로그들이니 참고해보길 권한다.
https://findstar.pe.kr/2022/09/17/points-to-consider-when-using-the-Spring-Events-feature/
https://wildeveloperetrain.tistory.com/246
이벤트 리스너 예외 처리가 조금은 아쉽지만 보통은 트랜잭션을 묶어서 처리하는걸 생각해보면 Transaction의 Phase와 Propagation 설정을 어떻게 조합하는지와 이에 따른 처리 방식을 아는 것이 더 중요해 보인다.
단순하게 사용할 수 있는 인터페이스일수록 추상화 수준이 높은 편인 경우가 많았다.
ApplicationEventPublisher와 EventListener는 그런 면에서 사용하기 굉장히 간편한 인터페이스 중 하나였다.
'공부 기록' 카테고리의 다른 글
[NEXTSTEP] ATDD 과정 1주차 시작 (0) | 2023.07.01 |
---|---|
[SPRINGXSECURITY] 스프링 시큐리티 "/" 경로 웰컴 페이지(index.html ) 허용하기 (0) | 2023.06.11 |
[SPRING] 스프링이 Event를 다루는 방법 - EventListener (0) | 2023.05.07 |
[SPRING] 스프링이 Event를 다루는 방법 - ApplicationEventPublisher (0) | 2023.05.07 |
[JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 3 (통합 테스트) (0) | 2023.02.15 |
댓글