스프링 이벤트 리뉴얼(1) - Publisher
- 개발
- 2026. 3. 18. 23:08
스프링 이벤트
스프링 이벤트(Spring Events)는 애플리케이션 내에서 컴포넌트 간의 느슨한 결합(loose coupling)을 유지하면서 비동기적 또는 동기적으로 메시지를 전달하는 메커니즘입니다.스프링의 Observer 패턴 기
b-programmer.tistory.com
이전에 스프링 이벤트를 학습한 적이 있습니다. 당시에는 이벤트가 어떻게 전달되고 실행되는지, 즉 내부 동작 방식에 집중해서 이해하려고 했습니다. 이번에는 조금 다른 관점에서 스프링 이벤트를 다시 바라보려고 합니다. 스프링 이벤트는 이벤트를 발행하는 퍼블리셔와 이를 처리하는 리스너로 구성되어 있으며, 이러한 구조는 메시지를 주고받는 MQ와 유사한 형태를 가지고 있습니다. 다만 스프링 이벤트는 Kafka나 RabbitMQ와 같은 외부 메시지 브로커를 사용하는 방식이 아니라, 동일한 애플리케이션 내부, 즉 하나의 JVM 자원을 공유하며 동작한다는 점에서 차이가 있습니다. 이번 글에서는 이러한 특징에 초점을 맞춰, 스프링 이벤트를 단순한 기능 설명이 아닌 애플리케이션 내부 메시지 전달 구조라는 관점에서 다시 정리해보려고 합니다.
스프링 이벤트란?
스프링 이벤트는 애플리케이션 내부에서 이벤트를 발행하고 이를 처리하기 위한 메커니즘입니다.
특정 로직에서 이벤트를 발행하면, 이를 구독하고 있는 리스너가 해당 이벤트를 받아 처리하는 구조로 동작합니다. 이 과정에서 발행자와 처리 로직이 직접적으로 연결되지 않기 때문에, 서로 간의 의존성을 낮출 수 있습니다.
즉, 스프링 이벤트는 하나의 기능을 여러 책임으로 나누고, 이를 느슨하게 연결하기 위한 방식입니다.
예를 들어 회원 가입이 완료된 이후, 포인트 지급이나 알림 발송과 같은 후속 작업을 이벤트로 분리하면, 기존 로직을 수정하지 않고도 기능을 확장할 수 있습니다.
Publisher

여기 publisher가 있습니다. String 메시지를 보내는 간단한 코드입니다. 그리고나서 동일한 이벤트 DTO를 사용하는 리스너로 이동하게 되어집니다. 그러면 어떻게 전달 할 수 있는걸까요? 마법이라도 부린 걸까요?
ApplicationEventPublisher부터 이해하도록 하겠습니다.
다음과 같은 동작 방식을 사용이 되어진다고 합니다.
publishEvent를 호출하게 되면 ApplicationContext로 전달이 된다고 합니다.
ApplicationContext:모든 객체(Bean)를 생성하고 관리하는 컨테이너

확인을 해보니 AbstractApplicationContext쪽으로 이동하게 됩니다.

현재 Event는 아쉽게도? 스프링 객체가 아닙니다. 여기서 드는 의문이 있습니다. 그러면 ApplicationContext에 넘겨주지 않아도 되지 않나 싶습니다. 조금더 정확하게 말하면, 넘겨주는 것이 아니라 발행하는것입니다.
비유를 하자면, 우체통에 편지를 넣는다고 해서 편지가 우체국 직원이 되는 건 아닙니다. 편지는 그냥 전달할 내용이고,
우체국 시스템이 그 편지를 적절한 곳으로 보내는 것입니다. 그렇다는 이야기는 ApplicationContext가 우체통이고, event는 편지라고 할 수 있겠네요. 코드를 자세히 보면 event자체가 ApplicationEvent도 될 수 있다는 사실을 알 수 있습니다.
사실 그전에는 event객체를 만들기 위해서는 AplicationEvent를 상속을 받아 이벤트를 만들었습니다. 하지만 지금의 방식은 dto객체를 전달할 뿐입니다.

확인해보면 역시 PayloadApplicationEvent로 applicationEvent를 만들어주고 있습니다.
그위 코드는 typeHint가 명시가 되었을 경우에 사용이 되어집니다. 고로 저희는 다음 코드를 읽을 수 있습니다.

사실 typeHint.. eventType 모두 null인 상태입니다. 고로 타입을 확인을 해줘야 합니다.

객체가 ResolvableTypeProvider를 구현하여 타입 정보를 직접 제공할 수 있다면 해당 타입을 사용하고, 그렇지 않다면 객체의 런타임 클래스 타입을 기반으로 판단합니다.

약간의아하는 곳이 있다고 여겨집니다. 바로 earlyApplicationEvents입니다. 이는 스프링의 초기화여부에 따라 결정이 되어집니다.
즉, 스프링 초기화가 이미 진행중이라면, null이 아니기 때문에 earlyApplicationEvents에 등록되어집니다. 그리고 나서 초기화가 완료가 되어지면 그제서야 이벤트를 발행이 되어집니다.
하지만, 일반적으로 springEvent를 사용할때는 필요가 없습니다. 왜냐하면 스프링 이벤트는 단순히 메시지(payload)만 발행하는 것이 아닙니다. 기본적으로 제공되어지는 스프링 이벤트들도 굉장히 많습니다. 예를 들면, 스프링 준비가 완료가 되었다, 스프링 환경 설정이 종료되었다는 등을 뜻합니다.
아무튼 그게 아니라면 applicationEventMulticaster를 통해 메시지를 발행이 되어진다는 것을 확인 할 수 있습니다.
자세히보면 eventType을 함께 전달 하고 있습니다. 이는 같은 eventType에게 모두 발행이 되어진다는 뜻이 되어집니다.
마지막으로 하위에 parent도 사실은 ApplicationContext입니다. 이말 뜻은 스프링에서 관리가 되어지는 객체라는 뜻입니다. 고로 이것 또한 스프링이 내부적으로 만든 이벤트들을 위해 만들어졌다고 생각하면 될거 같습니다. 간단하게 생각했을때, 자식이 하는 일을 부모도 알게 한다고 이해면 될거 같습니다.
ApplicationEventMulticaster

이제 거의 마지막 단계입니다.
단순히 publisher 하나의 흐름을 따라왔을 뿐인데, 생각보다 내부 구조가 길고 세분화되어 있다는 점을 확인할 수 있었습니다.
ApplicationEventMulticaster는 먼저 현재 이벤트와 타입 정보에 맞는 ApplicationListener들을 조회합니다.
이 과정에서 어떤 리스너가 해당 이벤트를 처리할 수 있는지 선별하게 되며, 내부적으로는 추가적인 필터링 로직이 존재하지만 여기서는 자세히 다루지 않겠습니다. 이후 실제 리스너 실행 단계로 넘어갑니다.
비동기 방식이라면 Executor를 통해 invokeListener를 실행하고, 동기 방식이라면 현재 호출 흐름에서 직접 invokeListener를 수행합니다.
결국 이 단계의 핵심 역할은 명확합니다.
ApplicationEventMulticaster는 이벤트를 처리할 리스너를 찾고, 동기 또는 비동기 방식에 맞춰 해당 리스너에게 이벤트를 전달하는 역할을 수행합니다.
마무리
특히 트랜잭션리스너와 일반 리스너의 차이에 대해 학습할 예정입니다.
'개발' 카테고리의 다른 글
| 결제 성공 콜백의 동시성 처리 안정화 (0) | 2026.03.20 |
|---|---|
| Stateless하게 개발하기 (0) | 2026.03.19 |
| 라이브락 vs 데드락 (0) | 2026.03.16 |
| 문제 발견: LazyConnectionDataSourceProxy 톺아보기 (1) | 2026.03.13 |
| Actuator 메트릭 생성 하기 (0) | 2026.03.11 |