스프링부트

application events and listeners

알쓸개잡 2023. 10. 2.

Spring framework는 애플리케이션 기동시 ContextRefreshedEvent 외에도 몇 가지 추가적인 이벤트를 전송한다. 이번 포스팅에서는 애플리케이션 기동시 발생하는 events와 이에 대한 listeners에 대해서 기록한다.

 

일부 event는 ApplicationContext가 생성되기 전에 발생하므로 해당 event에 대한 리스너를 @Bean으로 등록할 수 없다. ApplicationContext가 생성되기 전에 발생하는 event listener를 등록하려면 SpringApplication.addListeners(...) 메서드 또는 SpringApplicationBuilder.listeners(...) 메서드를 이용하여 등록할 수 있다.
애플리케이션이 생성되는 방식에 관계없이 해당 listener가 자동으로 등록되도록 하려면 resources/META-INF/spring.factories 파일을 추가하고 org.springframework.context.ApplicationListener 키를 사용하여 listener를 등록할 수 있다.
e.g) org.springframework.context.ApplicationListener=\
  com.example.event.listener.ApplicationEnvironmentPreparedEventListener, \
  com.example.event.listener.ApplicationContextInitializedEventListener, \
  com.example.event.listener.ApplicationPreparedEventListener

 

애플리케이션 이벤트 순서

애플리케이션 event는 애플리케이션 실행에 따라 다음 순서로 트리거 된다.

  • ApplicationStartingEvent
    • 애플리케이션 실행이 시작될 때 전송되지만 listener와 initializer의 등록을 제외한 모든 처리가 이루어지기 전에 전송된다.
  • ApplicationEnvironmentPreparedEvent
    • context가 생성되기 전 context 내의 Envirionment가 사용할 준비가 되었을때 전송된다.
  • ApplicationContextInitializedEvent
    • ApplicationContext가 준비되고 ApplicationContextInitializers가 호출되었지만 빈 정의가 로드되기 전에 전송된다.
  • ApplicationPreparedEvent
    • 빈 정의가 로드된 이후 ApplicationContext가 refresh 되기 직전에 전송된다.
  • ApplicationStartedEvent
    • ApplicationContext가 refresh 되었고 ApplicationRunner 혹은 CommandLineRunner가 호출되기 전에 전송된다.
  • AvailabilityChangeEvent
    • 애플리케이션이 라이브 상태임을 알리는 LivenessState.CORRECT 상태가 된 후에 전송된다.
  • ApplicationReadyEvent
    • ApplicationRunner 혹은 CommandLineRunner가 호출된 후에 전송된다.
  • AvailabilityChangeEvent
    • 애플리케이션이 요청을 처리할 준비가 되었음을 알리는 ReadinessState.ACCEPTING_TRAFFIC 상태가 된 후에 전송된다.
  • ApplicationFailedEvent
    • 시작시 예외가 발생한 경우에 전송된다.

아래의 event는 ApplicationPreparedEvent 이후와 ApplicationStartedEvent 이전에 전송된다.

  • WebServerInitializedEvent
    • 웹서버가 준비된 후 전송된다. ServletWebServerInitializedEvent와 ReactiveWebServerInitializedEvent는 각각 servlet 및 reactive 방식에 따라서 전송된다.
  • ContextRefreshedEvent
    • ApplicationContext가 refresh 될 때 전송된다.

 

Event listener는 기본적으로 동일한 스레드에서 실행되므로 Event listener에서 기동시 추가적인 작업을 수행하는 것보다  ApplicationRunner 혹은 CommandLineRunner를 통해서 추가적인 작업을 수행하는 것이 좋다.

애플리케이션 이벤트는 Spring framework의 event publishing 메커니즘을 사용하여 전송된다. 이 메커니즘의 일부는 자식 context의 listener에 게시된 event가 모든 상위 context의 listener에도 게시되도록 보장한다. 그 결과, 애플리케이션이 SpringApplication 인스턴스 계층 구조를 사용하는 경우 listener는 동일한 유형의 애플리케이션 이벤트 인스턴스를 여러 개 수신할 수 있다.

 

listener가 해당 context의 event와 하위 context의 event를 구분할 수 있도록 하려면 listener에 ApplicationContext를 주입되도록 한 다음 주입된 context를 event의 context와 비교해야 한다. ApplicationContext는 ApplicationContextAware를 implements 하여 주입하거나 listener가 bean인 경우 @Autowired를 사용하여 주입할 수 있다.

 

Listener 구현 샘플

ApplicationEnvirionmentPreparedEvent, ApplicationContextInitializedEvent, ApplicationPreparedEvent는 resources/META-INF/spring.factories에 listener를 등록하거나 SpringApplication의 addListeners 메소드를 통해서 listener를 등록해야 event를 받을 수 있다.

org.springframework.context.ApplicationListener=\
  com.example.event.listener.ApplicationEnvironmentPreparedEventListener, \
  com.example.event.listener.ApplicationContextInitializedEventListener, \
  com.example.event.listener.ApplicationPreparedEventListener

 

package com.example.event.listener;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;

@RequiredArgsConstructor
@Slf4j
public class ApplicationEnvironmentPreparedEventListener implements
	ApplicationListener<ApplicationEnvironmentPreparedEvent>, ApplicationContextAware {
	private ApplicationContext applicationContext;

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		log.info("received application environment prepared event, application context={}", applicationContext);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

 

package com.example.event.listener;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Slf4j
public class ApplicationPreparedEventListener implements
	ApplicationListener<ApplicationPreparedEvent>, ApplicationContextAware {
	private ApplicationContext applicationContext;

	@Override
	public void onApplicationEvent(ApplicationPreparedEvent event) {
		log.info("received application prepared event, application context={}, event context={}",
			applicationContext, event.getApplicationContext());
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

 

package com.example.event.listener;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;

@RequiredArgsConstructor
@Slf4j
public class ApplicationContextInitializedEventListener implements
	ApplicationListener<ApplicationContextInitializedEvent>, ApplicationContextAware {
	private ApplicationContext applicationContext;

	@Override
	public void onApplicationEvent(ApplicationContextInitializedEvent event) {
		log.info("received application context initialized event, application context={}, event context={}",
			applicationContext, event.getApplicationContext());
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

 

WebServerInitializedEvent부터는 @EventListener 어노테이션 지정으로 event를 수신할 수 있다.

package com.example.event.listener;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
public class AfterWebServerInitializedEventListener {
	private final ApplicationContext applicationContext;

	@EventListener
	public void onWebServerInitializedEvent(WebServerInitializedEvent event) {
		log.info("received web server initialized event by event listener");
		log.info("application context={}, event context={}", applicationContext, event.getApplicationContext());
	}

	@EventListener
	public void onContextRefreshedEvent(ContextRefreshedEvent event) {
		log.info("received context refreshed event by event listener");
		log.info("application context={}, event context={}", applicationContext, event.getApplicationContext());
	}

	@EventListener
	public void onApplicationStartedEvent(ApplicationStartedEvent event) {
		log.info("received application started event by event listener");
		log.info("application context={}, event context={}", applicationContext, event.getApplicationContext());
	}

	@EventListener
	public void onLivenessEvent(AvailabilityChangeEvent<LivenessState> event) {
		switch (event.getState()) {
			case CORRECT -> log.info("received LivenessState.CORRECT event");
			case BROKEN -> log.error("liveness probe failed");
			default -> log.error("undefined liveness availability state");
		}
	}

	@EventListener
	public void onApplicationReadyEvent(ApplicationReadyEvent event) {
		log.info("received application ready event by event listener");
		log.info("application context={}, event context={}", applicationContext, event.getApplicationContext());
	}

	@EventListener
	public void onReadinessEvent(AvailabilityChangeEvent<ReadinessState> event) {
		switch (event.getState()) {
			case ACCEPTING_TRAFFIC -> log.info("received ReadinessState.ACCEPTING_TRAFFIC event");
			case REFUSING_TRAFFIC -> log.error("readiness probe failed");
			default -> log.error("undefined readiness availability state");
		}
	}
}

 

위와 같이 event listener를 등록 후에 애플리케이션을 실행하면 아래와 같은 순서로 event가 발생 함을 확인할 수 있다.

//ApplicationContext가 생성되기 전이므로 application context는 null 이다.
licationEnvironmentPreparedEventListener : received application environment prepared event, application context=null
//ApplicationContext가 생성되기 전이므로 application context는 null 이다.
plicationContextInitializedEventListener : received application context initialized event, application context=null, event context=...AnnotationConfigServletWebServerApplicationContext@4a85eec0
c.e.e.l.ApplicationPreparedEventListener : received application prepared event, application context=...AnnotationConfigServletWebServerApplicationContext@4a85eec0, event context=...AnnotationConfigServletWebServerApplicationContext@4a85eec0
l.AfterWebServerInitializedEventListener : received web server initialized event by event listener
l.AfterWebServerInitializedEventListener : received context refreshed event by event listener
l.AfterWebServerInitializedEventListener : received application started event by event listener
l.AfterWebServerInitializedEventListener : received LivenessState.CORRECT event
l.AfterWebServerInitializedEventListener : received application ready event by event listener
l.AfterWebServerInitializedEventListener : received ReadinessState.ACCEPTING_TRAFFIC event

참고링크

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-events-and-listeners

 

Core Features

Spring Boot uses Commons Logging for all internal logging but leaves the underlying log implementation open. Default configurations are provided for Java Util Logging, Log4j2, and Logback. In each case, loggers are pre-configured to use console output with

docs.spring.io

 

댓글

💲 추천 글