최근 스프링 이벤트에 대한 관심이 생겨서 스프링 이벤트에 대해 정리해보려고 합니다.
Spring-Context의 클래스들과 내부의 event 패키지의 클래스들을 뜯어보고 데모 프로젝트를 통해 알아보았습니다.
어플리케이션에서 발생하는 특정 사건을 나타내는 이벤트 클래스를 정의합니다. 이 클래스는 ApplicationEvent
를 상속받습니다.
발생한 이벤트는 ApplicationEventPublisher
를 사용하여 ApplicationContext
에 등록된 리스너에게 발행됩니다.
/**
* 사용자 등록 이벤트 클래스
*/
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
public UserRegisteredEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
// 다른 메서드들...
// @Async, @Transactional, @Cacheable 등의 어노테이션을 사용할 수 있습니다.
public void registerUser(String username) {
// 사용자 등록 로직...
// 사용자 등록 이벤트 발생
UserRegisteredEvent userRegisteredEvent = new UserRegisteredEvent(this, username);
eventPublisher.publishEvent(userRegisteredEvent);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
}
어플리케이션에서는 특정 이벤트에 대한 처리를 수행하는 리스너를 구현합니다.
이 리스너는 ApplicationListener
를 구현하거나 @EventListener
어노테이션을 사용하여 메서드를 정의할 수 있습니다.
ApplicationListener
인터페이스 구현:메서드 시그니처에서 이벤트 타입을 지정해야 하지만, 외부에서 해당 리스너를 직접 호출할 수 있습니다.
import org.springframework.context.ApplicationListener;
public class UserRegisteredEventListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
// 사용자 등록 이벤트 처리 로직
String username = event.getUsername();
System.out.println("User registered: " + username);
}
}
위의 예제에서 UserRegisteredEventListener
클래스는 ApplicationListener
인터페이스를 구현하고, 제네릭으로 사용자 등록 이벤트 (UserRegisteredEvent
)를
지정합니다.
그리고 onApplicationEvent
메서드에서 해당 이벤트가 발생했을 때 수행할 로직을 구현합니다.
@EventListener
어노테이션 사용:@EventListener
어노테이션은 빈으로 등록된 클래스 내부의 메서드에만 적용 가능하며, 메서드의 시그니처에 이벤트 타입을 명시적으로 지정할 필요가 없습니다.
이는 편리하지만, 클래스 외부에서 해당 메서드를 호출할 수 없다는 제약이 있습니다.
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AnotherUserRegisteredEventListener {
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
// 다른 사용자 등록 이벤트 처리 로직
String username = event.getUsername();
System.out.println("Another user registered: " + username);
}
}
위의 예제에서 AnotherUserRegisteredEventListener
클래스는 @Component
어노테이션을 통해 빈으로 등록되며,
@EventListener
어노테이션을 사용하여 특정 이벤트에 대한 핸들러 메서드를 정의합니다. 메서드 이름은 자유롭게 정할 수 있습니다.
위와 같이 동일한 이벤트에 대해 여러 리스너가 등록되어 있을 때, @Order
어노테이션을 사용하여 리스너의 우선순위를 지정할 수 있습니다.
@Order(1)
을 @EventListener
어노테이션 위에 추가하여 리스너의 우선순위를 지정할 수 있습니다.
@EventListener({UserRegisteredEvent.class, OtherEvent.class})
와 같이 여러 이벤트 타입을 하나의 이벤트 리스너 메서드에 등록할 수 있습니다.
어플리케이션 컨텍스트가 초기화되면, 등록된 리스너들이 자동으로 등록된 이벤트에 대해 수신 대기 상태가 됩니다.
ApplicationContext
가 특정 상태에 도달하거나 특정 이벤트가 발생하면 등록된 리스너들이 해당 이벤트를 처리합니다.
ApplicationEventMulticaster
의 구현체를 명시적으로 지정하지 않으면, Spring은 기본적으로 단일 스레드에서 동작하는 SimpleApplicationEventMulticaster
를
사용합니다.
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
* ApplicationContext가 초기화 및 리프레시될 때 발생하는 이벤트를 처리하는 리스너입니다.
* 주로 초기화 작업이나 리소스 로딩과 관련된 작업을 수행합니다.
*/
@Component
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// ContextRefreshedEvent에 대한 처리 로직
System.out.println("ApplicationContext is refreshed.");
}
}
@Listener
어노테이션을 사용하여서도 구현이 가능합니다.
@Component
public class ContextRefreshedEventListener {
@EventListener
public void handleContextRefreshedEvent(ContextRefreshedEvent event) {
// ContextRefreshedEvent에 대한 처리 로직
System.out.println("ApplicationContext is refreshed.");
}
}
ContextRefreshedEventListener
는 ContextRefreshedEvent
를 처리하는 리스너로, @Component
어노테이션을 통해 빈으로 등록되었습니다. 이 리스너는
ApplicationContext
가 초기화 및 리프레시되면 해당 이벤트를 수신하여 처리합니다.
@Service
public class EventExampleService {
@Autowired
private ApplicationContext applicationContext;
public void performExample() {
applicationContext.publishEvent(new ContextRefreshedEvent(applicationContext));
// 다른 이벤트에 대한 발생 로직 추가 가능
}
}
EventExampleService에서는 ApplicationContext를 주입받아 publishEvent 메서드를 사용하여 ContextRefreshedEvent를 발생시킵니다.
이와 유사하게 ContextStartedEvent
, ContextStoppedEvent
, ContextClosedEvent
에 대한 리스너들도 구현할 수 있습니다.
이벤트 발행은 ApplicationEventMulticaster
인터페이스의 구현체를 통해 이루어집니다.
위 인터페이스의 추상 구현체인 AbstractApplicationEventMulticaster
를 사용하여 다양한 전파 방식을 제공합니다.
일반적으로 사용되는 구현체 중 하나는 SimpleApplicationEventMulticaster
입니다.
SimpleApplicationEventMulticaster
는 단일 스레드에서 동작하는 간단한 구현체로, 이벤트를 순차적으로 리스너에게 전달합니다.
import org.springframework.context.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.GenericApplicationContext;
public class EventPublisherExample {
public static void main(String[] args) {
// ApplicationContext 생성
GenericApplicationContext context = new GenericApplicationContext();
// SimpleApplicationEventMulticaster를 생성하고 ApplicationContext에 등록
ApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
context.addApplicationListener(new MyEventListener()); // 리스너 등록
context.setApplicationEventMulticaster(eventMulticaster);
// 이벤트 발행
context.refresh(); // ContextRefreshedEvent 발생
// ApplicationContext 종료
context.close(); // ContextClosedEvent 발생
}
}
SimpleApplicationEventMulticaster
를 생성하고, 해당 이벤트 멀티캐스터를 GenericApplicationContext
에 등록하였습니다.
그 후 context.refresh()
를 호출하여 ContextRefreshedEvent
를 발생시키면, 등록된 리스너인 MyEventListener
에서 해당 이벤트를 처리합니다.
마찬가지로 context.close()
를 호출하여 ContextClosedEvent
를 발생시키면, 해당 이벤트를 처리하는 리스너도 호출됩니다.
@EventListener
어노테이션을 사용하여 이벤트 리스너를 정의할 때, condition
속성을 사용하여 특정 조건을 만족하는 경우에만 해당 이벤트를 처리할 수 있습니다.
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ConditionalEventListener {
@EventListener(condition = "#root.event instanceof T(me.doxxx.springeventdemo.event.UserRegisteredEvent)")
public void handleConditionalEvent(ApplicationEvent event) {
String eventName = event.getClass().getSimpleName();
String expression = "#this.username.length() > 5";
if (EventExpressionEvaluator.evaluateExpression(expression, event)) {
System.out.println("Conditional event handled: " + eventName);
} else {
System.out.println("Conditional event not handled: " + eventName);
}
}
}
public class EventExpressionEvaluator {
private EventExpressionEvaluator() {
}
public static boolean evaluateExpression(String expression, Object event) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext(event);
return Boolean.TRUE.equals(parser.parseExpression(expression).getValue(context, Boolean.class));
}
}
위의 예제에서 ConditionalEventListener
는 @EventListener
어노테이션을 사용하여 UserRegisteredEvent
이벤트에 대한 리스너를 정의합니다.
그리고 EventExpressionEvaluator.evaluateExpression()
메서드를 사용하여 표현식을 평가하고 조건부로 이벤트를 처리합니다.
expression
변수에 설정된 표현식은 Spring EL 표현식으로, UserRegisteredEvent
클래스의 인스턴스이며, username
필드의 길이가 5보다 큰 경우에만 해당 이벤트를
처리합니다.