Spring Event
스프링 애플리케이션을 실행할 때 ApplicationContext
가 초기화되고 서버가 동작하게 되는 과정에서 내부적으로는 굉장히 많은 이벤트가 순차적으로 발생합니다. 스프링 프레임워크는 기본적으로 다음과 같은 이벤트들을 발행함으로써 자체적으로 ApplicationContext
의 라이프 사이클을 관리합니다.
- ContextRefreshedEvent: ApplicationContext가 초기화되거나 리프레시된 후(보통
context.refresh()
또는 애플리케이션 시작 시 발생) - ContextStartedEvent:
context.start()
가 호출될 때 발생 - ContextStoppedEvent:
context.stop()
이 호출될 때 발생 - ContextClosedEvent:
context.close()
또는 JVM 종료 시 발생(자원 정리 필요할 때 사용)
이러한 이벤트 외에도 스프링 부트에서는 다음과 같은 부트 전용 애플리케이션 초기화 이벤트들을 추가로 제공합니다.
- ApplicationStartingEvent: JVM 실행 직후, 가장 처음 발생
- ApplicationEnvironmentPreparedEvent:
Environment
가 준비된 후(프로파일 및 프로퍼티 확인 가능) - ApplicationContextInitializedEvent:
ApplicationContext
가 생성된 직후(Bean 등록 전) - ApplicationPreparedEvent:
ApplicationContext
에 Bean 등록 완료 후(context.refresh()는 아직 호출 전) - ApplicationStartedEvent:
context.refresh()
직후(대부분의 Bean 초기화 완료 시점) - ApplicationReadyEvent: 모든 애플리케이션 초기화 및
CommandLineRunner
,ApplicationRunner
까지 실행 완료 후 - ApplicationFailedEvent: 애플리케이션 부팅 중 Exception으로 실패했을 때 발생
이처럼 부트 초기화부터 서버가 가동되는 과정이 이벤트로 명확히 구분되어 있기 때문에 각각의 시점을 활용하여 초기화 커스터마이징, 외부 연동, 설정 검증 등 다양한 작업들을 안전하게 수행할 수 있습니다. 모든 이벤트는 내부적으로 ApplicationEventPublisher
를 통해 발행되며 이를 기반으로 스프링은 느슨하게 결합된 구조를 유지한 채 복잡한 초기화 흐름을 제어합니다.
하지만 이 구조는 우리가 직접 사용할 수 있는 확장 포인트이기도 합니다. `ApplicationListener` 인터페이스를 이용한 이벤트 발행은 단순히 `ApplicationContext` 라이프사이클 관리에만 국한되지 않고 개발자들이 직접 정의한 도메인 로직에도 확장해서 사용할 수 있도록 인터페이스와 어노테이션 기반의 추상화를 제공해줍니다. 스프링은 `@EventListener`라는 간단한 어노테이션 하나만으로 이 이벤트 시스템을 우리 코드에서 사용할 수 있게 해줍니다.
@EventListener
스프링은 오래전부터 이벤트 기반 프로그래밍을 지원해왔지만 초기에는 지금처럼 직관적이고 간단한 방식이 아니었습니다. 스프링 프레임워크가 발전하면서 이벤트 리스닝 방식도 점차 개선되어 왔고 현재는 @EventListener
라는 어노테이션 기반의 간결한 방식으로 자리잡았습니다.
@Component
class SampleService(
private val eventPublisher: ApplicationEventPublisher
) {
fun doSomething(id: Long) {
eventPublisher.publishEvent(SampleEvent(id))
}
}
@Component
class SampleEventListener : ApplicationListener<SampleEventEvent> {
override fun onApplicationEvent(event: SampleEventEvent) {
println("이벤트 수신: ${event.orderId}")
}
}
Spring 3.0 이전에서는 ApplicationEventPublisher
를 직접 DI 받아 이벤트를 발행하고 ApplicationListener
를 직접 구현한 리스너를 통해 이벤트를 구독하는 방식으로 이루어졌습니다. 하지만 스프링 4.2부터 @EventListener
어노테이션이 도입되면서 어노테이션 기반으로 메서드 단위에서 간결하게 이벤트를 처리할 수 있게 되었습니다.
@Component
class SampleEventHandler {
@EventListener
fun handleOrderCreated(event: SampleEvent) {
// 이벤트 처리 로직
}
}
결과적으로 @EventListener
는 단순히 문법적 편의성을 제공하는 수준이 아니라 이벤트 기반 구조의 확장성과 유지보수성을 고려했을 때도 현재 가장 권장되는 방식입니다.
동작 방식
스프링의 @EventListener
는 동기적 메소드 호출에 가깝습니다. 내부적으로는 이벤트를 발행하면 해당 이벤트를 처리할 수 있는 리스너 메소드를 찾아 리플랙션을 통해 직접 invoke()
메소드가 호출됩니다. 즉 스프링에서 기본적으로 여러 기능을 제공하는 방식에 일관되면서 단순하고 강력합니다.
동작 원리를 살펴보기 전에 먼저 다음과 같은 예제를 작성하겠습니다.
@Component
class SampleEventHandler {
@EventListener
fun handle(event: SampleEvent) {
println("message: ${event.message}")
}
}
data class SampleEvent(val message: String)
그 후 디버깅을 위한 이벤트를 실행할 테스트 코드를 작성합니다.
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@SpringBootTest
class SampleEventHandlerTest(
private val applicationEventPublisher: ApplicationEventPublisher,
) {
@Test
fun testPublishEvent() {
applicationEventPublisher.publishEvent(SampleEvent("hello world!"))
}
}
가장 먼저 applicationEventPublisher.publishEvent()
를 호출하게 되면 실제로는 AbstractApplicationContext.publishEvent()
추상 메소드를 호출함로써 발급한 이벤트에 관한 클래스 정보를 파싱합니다.
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
}
그 후 ApplicationEventMulticaster
를 통해서 이벤트를 여러 리스너에게 순차적으로 전달합니다. 즉 전달받은 이벤트 클래스 정보를 전달하면서 해당 이벤트를 처리할 수 있는 리스너를 찾아서 하나씩 호출하는 의미입니다.
이벤트를 처리할 수 있는 리스너를 찾으러갑니다.
먼저 리스너를 직접 찾기 전에 주어진 이벤트 타입과 소스 타입을 처리할 수 있는 캐싱된 리스너가 있는지 찾음과 동시에 해당 이벤트를 캐싱합니다.
현재 캐싱된 리스너가 없기 때문에 retrieveApplicationListeners()
를 통해 직접 리스너를 찾으러 갑니다.
beanFactory
에서 ApplicationListener
타입을 가진 빈을 찾아서 조회합니다. @EventListener
는 내부적으로 프록시 기반 ApplicationListenerMethodAdapter
로 래핑되기 떄문에 컴파일 시점에 ApplicationListener
를 구현하지 않지만 동일한 클래스 타입으로 인식되어 ApplicationContext
에 등록되기 때문에 beanFactory
에서 반환됩니다.
최종적으로 반환된 리스너의 함수를 invoke()
합니다.
이번 글에서는 스프링의 이벤트 시스템 관련 그리고 @EventListener
기반으로 어떤 방식으로 이벤트 리스너가 등록되고 실행되는지까지 살펴보았습니다.
단순한 어노테이션으로 시작된 이벤트 리스너가 어떤 흐름을 따라 실행되는지 디버깅하며 살펴봤고 그 내부에서 스프링의 bean과 컬렉션 형태를 가짐으로써 갖는 구조적인 유연성과 확장성을 더 깊이 체감할 수 있습니다.
오탈자 및 오류 내용을 댓글 또는 메일로 알려주시면, 검토 후 조치하겠습니다.
'Spring > Core' 카테고리의 다른 글
[Spring] @TransactionalEventListener (2) | 2025.07.23 |
---|---|
[Spring] @Transactional 속성 (1) | 2023.12.24 |
[Spring] Spring AOP 동작 방식 (0) | 2023.08.30 |
[Spring] Spring AOP JDK Dynamic & CGLIB Proxy 생성 방식 (0) | 2023.08.28 |
[Spring] Spring AOP란? (0) | 2023.08.27 |