우리가 작성한 수많은 서비스 객체들이 어느 시점에 생성되고 어떤 과정을 거쳐 완성되며 어떻게 서로 소통하는지를 아는 것은 프레임워크를 사용하는 단계에서 제어하는 단계로 넘어가는 핵심 역할을 할 수 있도록 한다. 이번 포스팅에서는 Bean의 생성 후 초기 작업(Bean Initialization)과 Bean 생성 시점에 틈새를 공략하는 확장 기법 그리고 부트스트래핑 과정에서 발생하는 이벤트와 이벤트 시스템(Event System)에 대해서 정리해 보고자 한다.
먼저 보면 좋을 포스팅
Spring Boot 시작 – SpringApplication 생성과 자동 감지
Spring Boot – Application Bootstraping을 위한 13단계
Bean Initialization 과정
BeanPostProcessor의 역할
아래 코드는 BeanPostProcessor 인터페이스를 나타낸다. BeanPostProcessor는 Spring Boot 내부에서 사용하는 구현체 외에도 Bean 생성 과정에서 사용자 정의 커스텀 로직을 추가할 수 있는 확장 포인트 역할을 한다.
BeanPostProcessor는 개별 Bean이 생성될 때마다 매번 실행된다. BeanPostProcessor에서 리턴하는 bean이 최종적으로 ApplicationContext에 Bean으로 등록된다.
public interface BeanPostProcessor {
//빈 객체가 생성되고 의존성 주입이 완료된 후 @PostConstruct나 afterPropertiesSet이 호출되기 직전에 실행된다.
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
//초기화 메서드(@PostConstruct등)가 실행된 직후에 실행된다. 주로 프록시 객체로 감싸거나(AOP) 최종 검증을 할 때 사용한다.
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}JavaSpring Boot – Application Bootstraping을 위한 13단계에서 10단계 ApplicationContext Refresh 과정에서 Spring Boot 내부적으로 사용하는 BeanPostProcessor 구현체들을 등록하는데 대표적인 구현체들은 다음과 같다.
- AutowiredAnnotationBeanPostProcessor
- 목적: 의존성 주입(DI)의 핵심 엔진
- 역할: @Autowired, @Value, @Inject 어노테이션을 감지하여 해당 필드나 메서드에 빈을 자동으로 주입한다.
- 실행시점: Bean 인스턴스가 생성된 직후 초기화 메서드(@PostConstruct등)가 실행되기 전에 동작한다.
- CommonAnnotationBeanPostProcessor
- 목적: 자바 표준(JSR-250) 어노테이션 지원
- 역할: @PostConstruct, @PreDestroy 어노테이션을 처리한다. 또한 @Resource 어노테이션을 통한 리소스 주입도 담당한다.
- 실행시점: 초기화 전후 단계에서 각각 생명주기 콜백 메서드를 호출한다.
- AnnotationAwareAspectJAutoProxyCreator
- 목적: AOP 프록시 생성
- 역할: @Aspect 어노테이션이 붙은 Bean을 분석하고 어드바이스가 적용될 대상 빈들을 찾아 프록시 객체로 바꾼다.
- 실행시점: postProcessAfterInitialization 단계에서 실제 객체를 프록시로 래핑하여 반환한다.
- ApplicationContextAwareProcessor
- 목적: 인프라 객체 주입
- 역할: Bean이 ApplicationContextAware, EnvironmentAware, ResourceLoaderAware등을 구현하고 있다면 해당 빈에게 ApplicationContext나, Environment 객체를 전달한다.
- ConfigurationClassPostProcessor
- 엄밀히 말하면 BeanFactoryPostProcessor이지만 가장 먼저 실행되어 @Configuration, @Bean, @Component, @Import를 스캔하고 Bean 정의를 생성하는 매우 중요한 역할을 한다.
InitializingBean과 @PostConstruct
다음은 Bean 초기화를 위한 두가지 방법을 나타낸다.
@Component
public class MyService implements InitializingBean {
@PostConstruct
public void postConstruct() {
// @PostConstruct가 먼저 실행됨
System.out.println("@PostConstruct executed");
}
@Override
public void afterPropertiesSet() throws Exception {
// InitializingBean.afterPropertiesSet()이 나중에 실행됨
System.out.println("afterPropertiesSet executed");
}
}Java실행 순서는 다음과 같다.
- 생성자 호출
- 의존성 주입
- BeanPostProcessor.postProcessBeforeInitialization()
- @PostConstruct 메서드 실행
- InitializingBean.afterPropertiesSet() 실행. (@PostConstruct가 우선 실행권을 가진다)
- 커스텀 init-method 실행 (@Bean 어노테이션의 initMethod 속성으로 지정된 메서드를 의미한다)
- BeanPostProcessor.postProcessAfterInitialization()
SmartInitializingSingleton
SmartInitializingSingleton은 모든 싱글톤 Bean이 만들어진 후에 딱 한번 실행되어야 하는 로직이 필요한 경우 사용할 수 있다.
SmartInitializingSingleton은 10단계의 ApplicationContext Refresh 과정의 마지막 시점인 finishBeanFactoryInitialization() 단계에서 동작한다. 마지막 과정인만큼 모든 Non-Lazy 싱글톤 Bean의 인스턴스화 및 의존성 주입, 초기화 콜백(@PostConstruct)이 완전히 끝난 후에 실행된다.
@PostConstruct 시점에는 아직 다른 Bean들이 생성 중일 수도 있기 때문에 만약 애플리케이션에 존재하는 모든 빈을 다 뒤져서 특정 작업을 해야 한다면 @PostConstruct는 위험하다. 아직 생성되지 않은 Bean이 있을 수 있기 때문이다.
아래 코드는 애플리케이션 구동 직후 등록된 모든 Controller를 찾아 특정 작업을 수행하는 샘플 코드다.
@Component
public class RouteConfigurationLogger implements SmartInitializingSingleton {
private final ListableBeanFactory beanFactory;
public RouteConfigurationLogger(ListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void afterSingletonsInstantiated() {
// 모든 빈이 이미 다 생성되었으므로 안전하게 모든 컨트롤러를 조회할 수 있다.
Map<String, Object> controllers = beanFactory.getBeansWithAnnotation(RestController.class);
System.out.println(">>> [최종 확인] 현재 등록된 모든 컨트롤러 수: " + controllers.size());
controllers.forEach((name, bean) -> {
// 상세 로직 처리...
});
}
}JavaSpring Boot Event System
Spring Boot는 애플리케이션 생명주기 동안 다양한 이벤트를 발행하여 확장성을 제공한다.
@EventListener 동작 원리
Spring의 이벤트 시스템은 Observer 패턴을 기반으로 하며 @EventListener 어노테이션이 지정된 메서드가 호출되는 과정은 다음과 같다.
@EventListener 등록 과정
- Bean 정의 로드 & 인스턴스 생성
- ApplicationContext를 초기화 하면서 모든 @Component, @Service, @Configuration 등의 빈을 생성 한다.
- 이 시점에는 @EventListener가 붙은 메서드는 아직 리스너로 등록되지 않은 상태다.
- EventListenerMethodProcessor Bean 후처리기 실행 (10단계 ApplicationContext Refresh)
- Spring Boot 자동 설정에 의해서 기본적으로 등록된다.
- 모든 Bean을 순회하며 @EventListener 어노테이션이 붙은 메서드를 스캔한다.
- 해당 메서드를 호출할 수 있는 ApplicationListener를 구현한 ApplicationListenerMethodAdapter 인스턴스를 생성한다. (ApplicationListenerMethodAdapter가 리스너 역할을 한다는 것이다.)
- ApplicationEventMulticaster에 ApplicationListenerMethodAdapter를 이벤트 리스너로 추가 등록한다.
- ApplicationListenerMethodAdapter 이벤트 수신
- ApplicationListenerMethodAdapter가 처리해야 할 Event가 발생하면 ApplicationEventMulticaster가 ApplicationListenerMethodAdapter에게 이벤트 발행 소식을 전달한다.
- ApplicationListenerMethodAdapter는 이벤트를 수신하면 @EventListener 메서드를 리플렉션으로 호출한다.
아래 코드는 EventListenerMethodProcessor 클래스를 나타낸다.
// 개별 Bean이 생성될 때마다 BeanPostProcessor가 동작
public class EventListenerMethodProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// Bean에서 @EventListener 어노테이션이 붙은 메서드 스캔
// 즉 @EventListener 어노테이션이 붙은 메서드를 가진 클래스는 Bean 이어야 함.
// Bean이 아니면 @EventListener는 동작하지 않음.
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(EventListener.class)) {
// ApplicationEventMulticaster에 리스너 등록
registerEventListener(bean, method);
}
}
return bean;
}
}Java위 코드를 보면 생성된 Bean에서 @EventListener 어노테이션이 붙은 메서드를 찾아 registerEventListener로 등록하는 것을 알 수 있다.
@EventListener 메서드 실행 메커니즘
Spring Boot는 @EventListener 어노테이션이 붙은 메서드를 처리하기 위한 ApplicationListener 인터페이스 구현체를 생성한다. (ApplicationListenerMethodAdapter) 핵심은 메서드 파라미터의 Event 타입을 분석해서 해당 이벤트가 발생했을 때만 메서드를 호출한다는 것이다. 즉 @EventListener 메서드의 시그니처와 발생한 이벤트가 동일한 Event인 메서드를 찾아서 호출된다는 것이다.
- ApplicationEventMulticaster에서 발행된 이벤트를 처리하는 리스너 찾기
- Event가 발행되면 ApplicationEventMulticaster는 등록된 모든 리스너의 명단을 훑는다.
- 이 때 리스너의 supportsEventType 메서드를 호출하여 해당 리스너가 처리하는 이벤트 타입을 얻어서 발행된 이벤트 타입과 비교하여 타입이 맞는 리스너(ApplicationListenerMethodAdapter)의 onApplicationEvent 메서드를 호출한다.
- ApplicationListenerMethodAdapter의 2차 검증(shouldHandle)
- Payload 체크: PayloadApplicationEvent인 경우 실제 데이터 타입이 맞는지 다시 확인한다.
- Condition 체크: @EventListener(condition = “#event.success == true”) 처럼 조건식이 붙어 있다면 이 조건까지 만족해야 @EventListener 메서드를 호출한다.
아래 ApplicationListenerMethodAdapter 클래스 코드의 주석을 참고하기 바란다.
// Spring이 내부적으로 생성하는 ApplicationListener 래퍼
public class ApplicationListenerMethodAdapter implements ApplicationListener<ApplicationEvent> {
private final Object targetBean;
private final Method targetMethod;
private final Class<?> eventType; // 파라미터에서 추출한 이벤트 타입
// @EventListener 메서드가 포함된 Bean과 method 정보를 설정한다.
public ApplicationListenerMethodAdapter(Object bean, Method method) {
this.targetBean = bean;
this.targetMethod = method;
// 메서드 파라미터에서 이벤트 타입 추출
this.eventType = method.getParameterTypes()[0];
}
...
...
// ApplicationEventMulticaster의 1차 필터(발행된 Event 타입과 파라미터 Event 타입이 맞는..) 통과시 호출
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (isDefaultExecution()) {
processEvent(event);
}
}
...
...
public void processEvent(ApplicationEvent event) {
// 발행된 이벤트가 메서드 파라미터와 일치하는지 확인하고 인자 리스트 생성
Object[] args = resolveArguments(event);
// @EventListener(condition="...") 등의 조건이 있다면 체크
// 여기서 2차 필터 동작
if (shouldHandle(event, args)) {
// 실제 Bean의 @EventListener 메서드 호출
Object result = doInvoke(args);
if (result != null) {
// 메서드 반환값이 있다면 그 반환값을 다시 새로운 이벤트로 발행 (Chaning)
handleResult(result);
}
else {
logger.trace("No result object given - no result to handle");
}
}
}
...
...
@Nullable
protected Object[] resolveArguments(ApplicationEvent event) {
// 현재 발행된 이벤트를 기반으로 파라미터에 정의된 이벤트 타입을 가져온다.
ResolvableType declaredEventType = getResolvableType(event);
if (declaredEventType == null) {
return null;
}
// @EventListener 메서드에 파라미터가 없다면 인자를 넘길 필요가 없으므로 빈 배열 반환
if (this.method.getParameterCount() == 0) {
return new Object[0];
}
// 메서드에 선언된 파라미터의 Class 타입을 가져옴
Class<?> declaredEventClass = declaredEventType.toClass();
// 메서드 파라미터가 ApplicationEvent를 상속받지 않았고 (즉, 일반 DTO나 String과 같은.)
// 메서드 파라미터가 PayloadApplicationEvent(내용을 담고 있는 이벤트)라면
if (!ApplicationEvent.class.isAssignableFrom(declaredEventClass) &&
event instanceof PayloadApplicationEvent<?> payloadEvent) {
// 발행된 이벤트에서 실제 데이터(payload)를 꺼낸다.
Object payload = payloadEvent.getPayload();
// 꺼낸 payload 타입이 메서드 파라미터 타입과 일치하는지 확인한다.
if (declaredEventClass.isInstance(payload)) {
// 일치한다면 이벤트객체가 아닌 데이터(payload)를 인자로 결정한다.
return new Object[] {payload};
}
}
return new Object[] {event};
}
}Java다음은 @EventListener를 사용하는 샘플코드다. 주석을 참고하기 바란다.
// @EventListener 메서드 - 파라미터 타입으로 이벤트 매핑
@Component
public class MyEventListener {
// ApplicationStartingEvent가 발행되면
// ApplicationEventMulticaster --> handleStarting 메서드 처리용 ApplicationListenerMethodAdapter 호출
// ApplicationListernerMethodAdapter에서 리플렉션으로 AplicationStartingEvent 객체와 함께 handleStarting을 호출
@EventListener
public void handleStarting(ApplicationStartingEvent event) {
log.info("Application is starting...");
}
// ApplicationReadyEvent가 발행되면
// ApplicationEventMulticaster --> handleReady 메서드 처리용 ApplicationListenerMethodAdapter 호출
// ApplicationListernerMethodAdapter에서 리플렉션으로 ApplicationReadyEvent 객체와 함께 handleReady를 호출
@EventListener
public void handleReady(ApplicationReadyEvent event) {
log.info("Application is ready...");
}
// ApplicationEvent가 발행되면
// ApplicationEventMulticaster --> handleAnyEvent 메서드 처리용 ApplicationListenerMethodAdapter 호출
// ApplicationListernerMethodAdapter에서 리플렉션으로 ApplicationEvent 객체와 함께 handleAnyEvent를 호출
// ApplicationEvent는 모든 ApplicationEvent 타입의 추상 클래스이므로 모든 ApplicationEvent 타입의 이벤트가 발생되면 호출
@EventListener
public void handleAnyEvent(ApplicationEvent event) {
log.info("Any application event occurred: {}", event.getClass().getSimpleName());
}
// Payload 기반 이벤트 처리 (POJO 방식)
// ApplicationEvent를 상속받지 않은 순수 객체(String, DTO 등)도 처리 가능하다.
// String 타입의 데이터를 포함한 이벤트가 발행되면
// 내부적으로 ApplicationListenerMethodAdapter의 resolveArguments가 payload를 추출합니다. (String 타입)
// ApplicationListernerMethodAdapter에서 리플렉션으로 String 데이터와 함께 handleStringPayload 호출.
@EventListener
public void handleStringPayload(String message) {
log.info("문자열 페이로드 이벤트 수신: {}", message);
}
// PayloadApplicationEvent 자체를 인자로 받는 경우
// 페이로드뿐만 아니라 이벤트의 부가 정보(타임스탬프 등)가 필요할 때 사용합니다.
@EventListener
public void handlePayloadEvent(PayloadApplicationEvent<MyCustomDto> event) {
MyCustomDto dto = event.getPayload();
log.info("PayloadApplicationEvent 래퍼를 통한 수신: {}, 발행시간: {}",
dto.getName(), event.getTimestamp());
}
}Java이벤트 발행 처리를 위한 추상화
앞선 포스팅에서 ApplicationEvent가 발행되었을 때 사용자 정의 처리를 위해서 ApplicationListener<ApplicationEvent>를 구현한 구현체 클래스를 정의하고 onApplicationEvent 메서드를 override 하여 처리하는 방식으로 소개했었다.
@EventListener 어노테이션은 ApplicationListener를 구현하는 방식을 대체할 수 있는 조금 더 편리하게 제공되는 추상화라고 볼 수 있다.
애플리케이션 부트스트래핑 과정에서 발생하는 이벤트 순서
- ApplicationStartingEvent: 애플리케이션 시작 직후 (1단계)
- ApplicationEnvironmentPreparedEvent: Environment 준비 완료 (6단계)
- ApplicationContextInitializedEvent: ApplicationContext 초기화 완료 (9단계)
- ApplicationContextPreparedEvent: ApplicationContext 준비, Bean 정의 로드 (Bean 생성 아님) (9단계)
- ContextRefreshedEvent: ApplicationContext Refresh 완료 (10단계)
- ApplicationStartedEvent: 애플리케이션 시작 완료 – ApplicationContext가 완전히 로드된 직후
- AvailabilityChangeEvent: 애플리케이션 가용성 상태 변경 (12단계)
- ApplicationReadyEvent: 애플리케이션 준비 완료 (13단계)
ApplicationStartingEvent 부터 ApplicationReadyEvent 까지
@EventListener 메서드의 각 ApplicationEvent를 파라미터로 지정하여 해당 이벤트 발생시 원하는 작업을 할 수 있다.
// 샘플 이벤트 리스너 구현 예시
@Component
@Slf4j
public class StartupEventListener {
//아래 이벤트는 호출되지 않는다.
//@Component로 Bean을 등록하는데 Bean이 생성되는 10단계 (ApplicationContext Refresh)
//과정에서 이 Bean이 생성되는데 ApplicationStartingEvent는 이 Bean이 생성되기
//이전에 발행되는 이벤트이기 때문이다.
@EventListener
public void handleApplicationStarting(ApplicationStartingEvent event) {
// 가장 먼저 발생하는 이벤트
// 로깅 시스템 초기화, 기본 설정 등
log.info("Application starting...");
}
//아래 이벤트 메서드는 호출되지 않는다.
//@Component로 Bean을 등록하는데 Bean이 생성되는 10단계 (ApplicationContext Refresh)
//과정에서 이 Bean이 생성되는데 ApplicationEnvironmentPreparedEvent는 이 Bean이 생성되기
//이전에 발행되는 이벤트이기 때문이다.
@EventListener
public void handleEnvironmentPrepared(ApplicationEnvironmentPreparedEvent event) {
// Environment가 준비되어 프로퍼티 접근 가능
ConfigurableEnvironment env = event.getEnvironment();
String profile = String.join(",", env.getActiveProfiles());
log.info("Active profiles: {}", profile);
}
// 아래 이벤트는 이 Bean이 생성된 이벤트이므로 호출된다.
@EventListener
public void handleApplicationReady(ApplicationReadyEvent event) {
// 애플리케이션이 완전히 준비됨
// 외부 시스템 연결, 헬스 체크 등록 등
log.info("Application ready to serve requests");
}
}Java이벤트 리스너 실행 순서 제어
여러 리스너가 같은 이벤트를 처리할 때 실행 순서를 제어할 수 있다.
@Component
public class OrderedEventListener {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE) // 가장 높은 우선순위
public void handleFirst(ApplicationReadyEvent event) {
log.info("First handler executed");
}
@EventListener
@Order(Ordered.LOWEST_PRECEDENCE) // 가장 낮은 우선순위
public void handleLast(ApplicationReadyEvent event) {
log.info("Last handler executed");
}
}Java우선 순위에 따라 handleFirst -> handleLast 순서로 실행 된다.
조건부 이벤트 처리
SpEL(Spring Expression Language)을 사용하여 조건부로 이벤트를 처리할 수 있다.
조건부 체크는 ApplicationListenerMethodAdapter의 shouldHandle 내에서 체크를 한다고 했다.
@Component
public class ConditionalEventListener {
/*
activeProfiles에 'dev'가 포함된 경우에만 이벤트를 처리한다.
*/
@EventListener(condition = "#event.environment.activeProfiles.contains('dev')")
public void handleDevOnly(ApplicationEnvironmentPreparedEvent event) {
log.info("This only runs in dev profile");
}
/*
애플리케이션 시작에 5초(5000ms) 이상 소요된 경우에만 이벤트를 처리한다.
*/
@EventListener(condition = "#event.timeTaken.toMillis() > 5000")
public void handleSlowStartup(ApplicationStartedEvent event) {
log.warn("Application took more than 5 seconds to start: {} ms",
event.getTimeTaken().toMillis());
}
}Java- event.environment.activeProfiles
- ApplicationEnvironmentPreparedEvent 객체에 전달된 environment(Environment 객체)를 바로 access 한다.
- Environment 객체에 설정된 활성화된 프로파일(active profiles) 정보를 얻을 수 있다.
커스텀 이벤트 리스너 구현 패턴
어노테이션 기반 리스너
앞에서 살펴봤던 @EventListener 어노테이션 메서드를 통해서 이벤트 리스너를 구현할 수 있다.
@Component
public class CustomEventListener {
//Application Bootstraping이 끝난 후 ApplicationReadyEvent를 받아서 처리할 수 있다.
@EventListener
@Async
public void handleAsyncEvent(ApplicationReadyEvent event) {
// 비동기 처리
}
//Application이 시작된 직후 ApplicationStartedEvent를 받아서 처리할 수 있다.
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handleHighPriorityEvent(ApplicationStartedEvent event) {
// 높은 우선순위로 처리
}
}JavaApplicationListener 인터페이스 구현
Spring Boot – Application Bootstraping을 위한 13단계 예제 코드에서 처리한 방식으로 리스너를 처리할 수 있다.
@Component
public class TypedEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// 타입 안전한 이벤트 처리
log.info("Application ready at: {}", event.getTimeTaken());
}
}Java@EventListener와 ApplicationListener 등록 차이
@EventListener를 포함하는 클래스는 반드시 스프링 Bean(@Component)으로 등록을 해야 한다.
그리고 @Component와 addListener 혹은 /META-INF/spring.factories 등록 차이
ApplicationListener를 구현한 경우에는 스프링 Bean(@Component) 형태로 등록하거나 META-INF/spring.factories에 등록하거나 SpringApplication의 addListener를 통해서 추가할 수 있다.
그리고 @Component로 등록한 경우와 META-INF/spring.factories 혹은 addListener를 통해서 적용하는 경우 차이는 @Component로 등록한 경우에는 10단계(ApplicationContext Refresh) 과정에서 리스너가 생성되므로 10단계 부터 이후에 발생되는 이벤트를 처리할 수 있다.
반면에 addListener혹은 META-INF/spring.factories를 통해 ApplicationListener를 등록한 경우에는 애플리케이션 Bootstraping 초기부터 등록되어 사용되므로 모든 이벤트를 받아서 처리할 수 있다는 차이가 있다.
사용자 정의 이벤트 사용하기
Application Bootstraping 과정에서 발생하는 이벤트 리스너 외에 애플리케이션 구동 후 진행되는 런타임에 사용자가 직접 정의한 이벤트를 발행하여 처리할 수 있다.
이벤트 클래스 정의 (TaskCompletionEvent)
@Getter
public class TaskCompletionEvent {
private final Long taskId;
private final String userEmail;
public TaskCompletionEvent( Long taskId, String userEmail ) {
this.taskId = taskId;
this.userEmail = userEmail;
}
}JavaTask 작업이 완료되었을 때 발행하기 위한 이벤트를 정의한다.
이벤트 발행 (TaskService)
Task 작업을 수행 후 완료되면 이벤트를 발행한다.
@Service
public class TaskService {
// publisher에는 보통 ApplicationContext 구현체가 주입됩니다.
// ApplicationContext 인터페이스는 ApplicationEventPublisher를 확장합니다.
private final ApplicationEventPublisher publisher;
public TaskService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void goTask(Long orderId, String email) {
// task 처리 로직
// task 처리가 완료 되면 TaskCompletionEvent 발행
publisher.publishEvent(new TaskCompletionEvent(orderId, email));
}
}Javapublisher.publishEvent 호출로 이벤트를 발행하면 다음과 같은 처리가 일어난다.
- 이벤트가 발행되면 ApplicationEventMulticaster가 등록된 ApplicationListener 목록에서 이벤트 타입(여기서는 TaskCompletionEvent)에 맞는 리스너를 찾는다. @EventListener로 리스너를 정의했다면 ApplicationListenerMethodAdapter 타입의 리스너일 것이다.
- 매칭된 리스너(ApplicationListenerMethodAdapter) 호출을 통해서 리플렉션으로 TaskCompletionEvent를 전달하면서 메서드를 호출한다.
이벤트 리스너 (TaskCompletionEventListener)
@Component
public class TaskEventListener {
// TaskCompletionEvent 발생시 호출됩니다.
@EventListener
public void onTaskCompleted(TaskCompletionEvent event) {
//task 완료 후 처리
// 동기 처리 (기본)
// 예: 이메일 큐 적재, 감사 로그 기록 등
}
}Java@EventListener 메서드에서 이벤트 발행을 통해서 전달된 TaskCompletionEvent를 전달 받아 처리를 한다.
지금까지 Bean 생성시 초기화와 Spring Boot Event System을 통해서 @EventListener 어노테이션 메서드가 어떤 흐름을 통해서 호출이 되는지에 대해서 정리해 보았다.
Spring Boot 애플리케이션을 기동하기 위해서는 단 한줄의 코드면 되지만 그 한줄의 코드안에는 엄청나게 많은 일들이 일어나고 있음을 알 수 있었다. Spring Boot Application이 어떤 Bootstrap 과정을 거치는지를 알고 있으면 기동 시점에 어떤 특정 작업을 해야 하거나 일반적이지 않은 설정 처리 작업을 해야 하는 경우에 유연하게 대처할 수 있다.
먼저 보면 좋은 포스팅
Spring Boot 시작 – SpringApplication 생성과 자동 감지
Spring Boot – Application Bootstraping을 위한 13단계
