본문 바로가기

Spring Boot/비동기 처리

Spring Event : @EventListener 활용

@EventListener

 

 - @EventListener 애너테이션의 역할, 동작 방식

 - 메서드 기반 이벤트 리스너를 구현, 실행 흐름 파악

 - 조건부 이벤트 리스너 구현(condition 속성)

 - SpEL(Spring Expression Language)를 활용해 이벤트 필터링 구현

 - 이벤트 기반 구조에서 유연성, 확장성을 높이는 방법

 


@EventListener

이벤트 클래스, 퍼블리셔, 리스너 이후에 애너테이션을 활용하여 메서드 단위로 이벤트를 처리하는 법

@EventListener는 기존의 ApplicationListener를 대체하여 더 간결하고 가독성 높은 방식으로 이벤트 리스너를 작성하게 해줌

 

1. 메서드 기반 이벤트 리스너

implements를 활용한 기존의 이벤트 리스너 구현

public class UserRegisteredEventListener 
    implements ApplicationListener<UserRegisteredEvent> {
    
    @Override
	public void onApplicationEvent(UserRegisteredEvent event) {
    	System.out.println("회원가입 이벤트 수신" + event.getEmail());
        // 이후 비즈니스 로직 구현
    }
}

 

 

Spring 4.2 이후 @EventListener를 메서드에 붙이는 방식

@Component
public class UserEventHandler {
	
    @EventListener // UserRegisteredEvent 타입의 이벤트가 발생할 때 자동으로 실행된다고 함
    public handlerUserRegistered(UserRegisteredEvent event) {
    	// 대충 비즈니스 로직
        System.out.println("회원가입 완료" + event.getEmail());
    }
}

 

 

방식의 차이에 대한 비교

비교 ApplicationListener implements 방식 @EventListener 애너테이션 방식
작성 구조 클래스 단위로 구현 메서드 단위로 구현
가독성 낮음(상속 필요) 높음(애너테이션만으로 구현)
유지보수 이벤트 타입 추가시 클래스를 추가해줘야함 한 클래스에서 여러 이벤트 처리 가능

 

 

 

2. 하나의 클래스에서 여러 이벤트 처리하기

@EventListener는 메서드에 붙이는 애너테이션이기 때문에 하나의 클래스에서 여러 메서드에 @EventListener를 붙여주면 여러 이벤트를 처리할 수 있다.

다만 트랜잭션에서 봤던 Self-Invocation에 대해서 고려해보면 좋을것이다.

@Component
public class UserEventHandler {
	
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
    	System.out.println("회원가입 완료 : " + event.getEmail());
    }
    
    @EventListener
    public void handleUserDeleted(UserDeletedEvent event) {
    	System.out.println("회원탈퇴 완료 " + event.getUserId());
    }
}

 

이런식으로 하나의 리스너 클래스에서 CRUD를 구현할 수 있다.

 

 

3. 디렉토리 구조 예시

src/main/java/com/example/demo/

event/

UserRegisteredEvent.java

UserDeletedEvent.java

UserEventHandler.java < - 여러 이벤트를 처리하는 클래스, 분리도 가능하다

service/

UserService.java < - 이벤트 발행자, 퍼블리셔


조건부 이벤트 처리(Condition 속성)

@EventListener 애너테이션은 condition 속성을 활용해서 특정 조건일때만 이벤트를 처리할 수 있다고 함

 

1. 기본 구조

public class ConditionalUserEventHandler {
	
    @EventListener(condition = "#event.premium == true") // 여기
    public void handlePremiumUser(UserRegisteredEvent event) {
    	System.out.println("프리미엄 회원 처리 " + event.getEmail());
    }
}

 

UserRegisteredEvent의 premium 필드가 true일때만 실행되는 이벤트 리스너이다.

 

이벤트를 발행할 때(서비스에서 퍼블리싱 할 때) 프리미엄의 여부를 함께 전달하면 이벤트로 받을 수 있을 것이다.

// 서비스
publisher.publishEvent(new UserRegisteredEvent("b1uffer@example.com", true)); // 프리미엄 true

 

이 때 premium == false라면 이벤트가 무시된다.

 


SpEL(spring Expression Language) 활용

SpEL은 런타임에 객체의 값을 표현식으로 평가할 수 있는 강력한 기능을 제공해준다고 한다.

@EventListener(condition = "") 내부에 SpEL을 활용하면 이벤트 속성을 기반으로 정교한 조건식을 만들 수 있다.

 

1. SpEL 문법 기본

문법 설명 예시
#event 현재 이벤트 객체를 참조함 #event.email
논리 연산 조건 조합 #event.age > 20 and #event.active
문자열 연산 문자열을 비교함 #event.email matches '.*@vip.com'

 

 

2. 예시

@Component
public class VipUserEventHandler {
	
    // 이메일이 특정 도메인(@vip.com)일 때만 실행한다. 정규식을 활용한거임
    @EventListener(condition = "#event.email matches '.*@vip.com'")
    public void handleVipUser(UserRegisteredEvent event) {
    	System.out.println("VIP 회원입니다. " + event.getEmail());
    }
}

 

이메일 주소가 @vip.com으로 끝나는 사용자에 대해서만 리스너가 반응한다.

 


이벤트 리스너 호출 흐름

@EventListener 애너테이션에 대한 이벤트 리스너 호출 흐름

 

퍼블리셔 - 스프링 컨테이너 - 이벤트 - 리스너 형태가 될 것인데, 여기에 @EventListener가 껴있을 뿐이다.

 


요소 역할 예시
@EventListener 이벤트를 메서드 단위로 처리함 handleUserRegistered()
condition 속성 이벤트 처리 조건 지정 condition = #event.premium == true
SpEL 동적 조건 표현식 평가 #event.email matches '.*@vip.com'
ApplicationEventPublisher 이벤트 발행 및 전달 publishEvent()