Spring中如何自定義監(jiān)聽器
前言
通過一個簡單的自定義的監(jiān)聽器,從源碼的角度分一下Spring中監(jiān)聽的整個過程,分析監(jiān)聽的作用。
一、自定義監(jiān)聽案例
1.1定義事件
package com.lazy.snail;
?
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
?
/**
* @ClassName UserRegisteredEvent
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:37
* @Version 1.0
*/
@Getter
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
?
public UserRegisteredEvent(Object source, String username) {
super(source);
this.username = username;
}
}
1.2定義監(jiān)聽
package com.lazy.snail;
?
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
?
/**
* @ClassName UserRegisteredListener
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:36
* @Version 1.0
*/
@Component
public class UserRegisteredListener {
@EventListener
public void handleUserRegisterEvent(UserRegisteredEvent event) {
System.out.println("用戶注冊成功,發(fā)送郵件通知");
}
}
1.3定義用戶服務(wù)(發(fā)布事件)
package com.lazy.snail;
?
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
?
/**
* @ClassName UserService
* @Description TODO
* @Author lazysnail
* @Date 2024/11/8 10:37
* @Version 1.0
*/
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
?
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
?
public void registerUser(String username) {
// 用戶注冊邏輯
System.out.println("Registering user: " + username);
?
// 發(fā)布用戶注冊事件
eventPublisher.publishEvent(new UserRegisteredEvent(this, username));
}
}
1.4測試類
package com.lazy.snail;
?
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
?
@Slf4j
public class SpringTest {
?
@Test
void test() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.getBean(UserService.class).registerUser("lazysnail");
}
}
1.5測試結(jié)果

二、事件監(jiān)聽流程
2.1容器啟動階段
2.1.1事件監(jiān)聽方法處理器及默認事件監(jiān)聽工廠
事件監(jiān)聽方法處理器及默認事件監(jiān)聽工廠的bean定義信息注冊
事件監(jiān)聽方法處理器會在后續(xù)用于處理自定義監(jiān)聽中的@EventListener注解
默認事件監(jiān)聽工廠會用于將自定義監(jiān)聽封裝為ApplicationListenerMethodAdapter
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 省略部分代碼...
// 事件監(jiān)聽方法處理器
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
// 默認事件監(jiān)聽工廠
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
?
return beanDefs;
}
事件監(jiān)聽方法處理器及默認事件監(jiān)聽工廠的實例化
refresh方法中,invokeBeanFactoryPostProcessors處理BeanFactoryPostProcessor(EventListenerMethodProcessor實現(xiàn)了BeanFactoryPostProcessor)
實例化EventListenerMethodProcessor
調(diào)用EventListenerMethodProcessor的postProcessBeanFactory實例化DefaultEventListenerFactory

// EventListenerMethodProcessor
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
?
Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
List<EventListenerFactory> factories = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(factories);
this.eventListenerFactories = factories;
}
2.1.3應(yīng)用事件廣播器創(chuàng)建
容器刷新時,initApplicationEventMulticaster創(chuàng)建SimpleApplicationEventMulticaster
注冊單例到容器
// AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException {
// 為容器初始化事件廣播器
initApplicationEventMulticaster();
}
// AbstractApplicationContext
protected void initApplicationEventMulticaster() {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
SimpleApplicationEventMulticaster從AbstractApplicationEventMulticaster繼承過來一個defaultRetriever對象
defaultRetriever中封裝了監(jiān)聽器集合
private class DefaultListenerRetriever {
?
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
?
public final Set<String> applicationListenerBeans = new LinkedHashSet<>();
}
監(jiān)聽集合中的監(jiān)聽是何時添加的
提前實例化單例后EventListenerMethodProcessor對容器中所有監(jiān)聽處理時添加
// DefaultListableBeanFactory
public void preInstantiateSingletons() throws BeansException {
// 省略部分代碼...
// EventListenerMethodProcessor
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
} else {
// 單例實例化后處理
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}
監(jiān)聽器的創(chuàng)建
// EventListenerMethodProcessor
public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
// 處理UserRegisteredListener
for (String beanName : beanNames) {
// 省略部分代碼...
processBean(beanName, type);
}
}
?
private void processBean(final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType) &&
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
!isSpringContainerClass(targetType)) {
?
Map<Method, EventListener> annotatedMethods = null;
// 省略部分代碼...
// @EventListener注解的方法(注解上的屬性)
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
?
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
} else {
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
Assert.state(factories != null, "EventListenerFactory List not initialized");
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 事件監(jiān)聽工廠創(chuàng)建應(yīng)用監(jiān)聽器 ApplicationListenerMethodAdapter
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 添加到應(yīng)用上下文
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
}
2.2客戶端調(diào)用階段
發(fā)布事件
// AbstractApplicationContext
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
拿到內(nèi)部應(yīng)用事件廣播器(SimpleApplicationEventMulticaster)
廣播器廣播事件
// SimpleApplicationEventMulticaster
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
獲取監(jiān)聽
檢索應(yīng)用監(jiān)聽器
直接從檢索器(defaultRetriever)中取出監(jiān)聽
/**
* 根據(jù)給定的事件、源(我理解是容器)檢索監(jiān)聽器
*
*/
// AbstractApplicationEventMulticaster
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
?
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
?
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.defaultRetriever) {
// 默認檢索器中獲取應(yīng)用監(jiān)聽,監(jiān)聽已經(jīng)在Spring啟動階段注冊完成
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
?
// 省略部分代碼...
?
AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null) {
if (filteredListenerBeans.isEmpty()) {
retriever.applicationListeners = new LinkedHashSet<>(allListeners);
retriever.applicationListenerBeans = filteredListenerBeans;
}
else {
retriever.applicationListeners = filteredListeners;
retriever.applicationListenerBeans = filteredListenerBeans;
}
}
return allListeners;
}
調(diào)用監(jiān)聽
invokeListener

三、總結(jié)
個人理解:
事件發(fā)布是一個抽象的概念,真正將事件發(fā)布出去的是SimpleApplicationEventMulticaster,發(fā)布事件實際做的事情,找到監(jiān)聽器,過濾出能夠處理這個事件的監(jiān)聽器,然后執(zhí)行監(jiān)聽器中針對這個事件的業(yè)務(wù)邏輯。
3.1監(jiān)聽流程總結(jié)
3.1.1. Spring 容器啟動
- 在 Spring 啟動過程中,
ApplicationContext被初始化,它作為核心容器,提供了事件發(fā)布和監(jiān)聽的機制。 - Spring 使用
ApplicationEventPublisher作為事件發(fā)布的核心接口,事件的發(fā)布與處理都在ApplicationContext內(nèi)部實現(xiàn)。
3.1.2. 監(jiān)聽器注冊
在 Spring 中,可以通過以下幾種方式注冊監(jiān)聽器:
- 實現(xiàn) ApplicationListener 接口:將實現(xiàn)類作為 Spring Bean 注冊,Spring 會自動將它識別為事件監(jiān)聽器。
- 通過 XML 配置:在 XML 文件中配置
<bean class="com.example.MyEventListener"/>,將監(jiān)聽器注冊到ApplicationContext。 - 注解方式:使用@EventListener注解
監(jiān)聽器的作用:每當發(fā)布的事件類型與監(jiān)聽器泛型參數(shù)中的事件類型匹配時,監(jiān)聽器的 onApplicationEvent 方法就會被調(diào)用。
3.1.3. 事件發(fā)布
發(fā)布者:Spring 中的任何組件都可以通過 ApplicationEventPublisher 發(fā)布事件。通常,ApplicationContext 本身實現(xiàn)了 ApplicationEventPublisher,可以直接調(diào)用 publishEvent() 發(fā)布事件。
事件傳播器:默認情況下,Spring 使用 SimpleApplicationEventMulticaster 作為事件傳播器,它負責(zé)查找符合條件的監(jiān)聽器并將事件分發(fā)給它們。
發(fā)布事件的方法:通過 applicationContext.publishEvent(new CustomEvent(this)) 來發(fā)布事件。
3.1.4. 事件廣播給監(jiān)聽器
- 篩選監(jiān)聽器:
SimpleApplicationEventMulticaster會檢查所有注冊的監(jiān)聽器,篩選出對當前事件感興趣的監(jiān)聽器(基于事件類型的匹配)。 - 同步與異步:在 Spring 環(huán)境中,默認情況下事件是同步傳遞的,所有監(jiān)聽器在主線程中執(zhí)行。如果需要異步,可以通過自定義
SimpleApplicationEventMulticaster并配置線程池。
3.1.5. 監(jiān)聽器處理事件
- 監(jiān)聽邏輯執(zhí)行:每個匹配的監(jiān)聽器會調(diào)用
onApplicationEvent()方法,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。 - 異常處理:如果監(jiān)聽器拋出異常,
SimpleApplicationEventMulticaster會捕獲并記錄日志,但不會影響其他監(jiān)聽器的執(zhí)行。
3.1.6. 事件傳播的擴展
在某些場景中,一個事件的監(jiān)聽器可能會發(fā)布新的事件,這會形成事件鏈。Spring 容器會遞歸地將這些新事件廣播給感興趣的監(jiān)聽器。
3.2應(yīng)用場景
3.2.1. 解耦業(yè)務(wù)邏輯
- 場景描述:在業(yè)務(wù)流程中,常常需要在某個操作完成后執(zhí)行附加邏輯,比如用戶注冊后發(fā)送歡迎郵件、推送通知、或更新統(tǒng)計數(shù)據(jù)。
- 實現(xiàn)方式:通過監(jiān)聽器監(jiān)聽用戶注冊事件,執(zhí)行后續(xù)的附加操作。這樣,核心業(yè)務(wù)邏輯與附加邏輯可以解耦,各自獨立管理。
- 示例:用戶注冊成功后觸發(fā)
UserRegistrationEvent,監(jiān)聽器接收事件后完成發(fā)送郵件或通知的任務(wù)。
3.2.2. 事務(wù)性事件
- 場景描述:在某些情況下,需要確保只有當事務(wù)成功提交后,才會發(fā)布事件。比如在訂單創(chuàng)建后,確保庫存減少或通知支付系統(tǒng)。
- 實現(xiàn)方式:通過 @TransactionalEventListener 監(jiān)聽事務(wù)性事件,確保事件只有在事務(wù)提交成功時才會觸發(fā)。
- 示例:訂單創(chuàng)建完成并且數(shù)據(jù)庫事務(wù)成功提交后,觸發(fā)
OrderCreatedEvent,通知庫存系統(tǒng)減少庫存。
3.2.3. 異步處理任務(wù)
- 場景描述:對于不需要實時完成的任務(wù),可以通過異步監(jiān)聽器來解放主線程,避免阻塞。
- 實現(xiàn)方式:在事件監(jiān)聽器方法上使用 @Async,使其在獨立線程中執(zhí)行異步任務(wù)。
- 示例:用戶在系統(tǒng)中上傳文件,文件處理邏輯通過事件異步執(zhí)行,以保證上傳接口的快速響應(yīng)。
3.2.4. 應(yīng)用啟動或關(guān)閉事件
- 場景描述:在應(yīng)用啟動或關(guān)閉時,通常需要執(zhí)行一些初始化或清理操作,比如加載配置、檢查依賴服務(wù)、關(guān)閉資源等。
- 實現(xiàn)方式:通過監(jiān)聽 ApplicationReadyEvent、ContextClosedEvent 等應(yīng)用上下文事件,實現(xiàn)啟動和關(guān)閉時的操作。
- 示例:在應(yīng)用啟動完成后加載配置文件,或在應(yīng)用關(guān)閉時清理緩存或關(guān)閉數(shù)據(jù)庫連接。
3.2.5. 狀態(tài)變化或監(jiān)控
- 場景描述:在系統(tǒng)中監(jiān)控某些狀態(tài)的變化,比如監(jiān)控服務(wù)狀態(tài)、資源使用情況、流量變化等。
- 實現(xiàn)方式:使用自定義事件來捕獲和廣播狀態(tài)變化,監(jiān)聽器實時響應(yīng)狀態(tài)變化,執(zhí)行對應(yīng)操作。
- 示例:當服務(wù)發(fā)現(xiàn)高負載時,發(fā)布
HighLoadEvent,監(jiān)聽器響應(yīng)并調(diào)整系統(tǒng)參數(shù)或生成告警。
3.2.6. 領(lǐng)域驅(qū)動設(shè)計(DDD)中的事件處理
- 場景描述:在領(lǐng)域驅(qū)動設(shè)計中,事件驅(qū)動架構(gòu)常用于處理不同領(lǐng)域的事件交互,比如訂單模塊的事件會影響到支付、物流等模塊。
- 實現(xiàn)方式:通過領(lǐng)域事件(如訂單支付事件、庫存更新事件)來實現(xiàn)模塊間的松耦合通信,避免模塊之間的直接依賴。
- 示例:在電商系統(tǒng)中,用戶下單后觸發(fā)
OrderPlacedEvent,物流模塊監(jiān)聽該事件并安排發(fā)貨。
3.2.7. 跨服務(wù)通信
- 場景描述:在微服務(wù)架構(gòu)中,服務(wù)之間往往需要基于事件進行異步通信,降低耦合度。
- 實現(xiàn)方式:通過發(fā)布事件到消息中間件(如 Kafka、RabbitMQ),各服務(wù)監(jiān)聽感興趣的事件。
- 示例:支付服務(wù)完成支付后觸發(fā)
PaymentCompletedEvent,訂單服務(wù)監(jiān)聽該事件并更新訂單狀態(tài)。
3.2.8. 監(jiān)聽應(yīng)用配置變化
- 場景描述:在應(yīng)用運行期間,可能需要動態(tài)刷新配置,比如數(shù)據(jù)庫連接、緩存配置等。
- 實現(xiàn)方式:通過監(jiān)聽配置中心的配置更新事件,觸發(fā)配置的刷新。
- 示例:當配置中心檢測到 Redis 緩存配置更新后觸發(fā)
CacheConfigUpdateEvent,應(yīng)用的緩存配置自動刷新。
3.2.9. 處理安全或認證事件
- 場景描述:在用戶認證、權(quán)限驗證等過程中,可以發(fā)布事件來處理安全相關(guān)操作。
- 實現(xiàn)方式:監(jiān)聽認證成功、認證失敗等事件,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,比如記錄日志、鎖定賬戶。
- 示例:用戶多次登錄失敗后觸發(fā)
AuthenticationFailureEvent,監(jiān)聽器響應(yīng)后鎖定用戶賬戶并生成告警。
以上就是Spring中如何自定義監(jiān)聽器的詳細內(nèi)容,更多關(guān)于Spring自定義監(jiān)聽器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用mybatis報Invalid bound statement解決分析
這篇文章主要為大家介紹了使用mybatis報Invalid bound statement原因解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
Java實現(xiàn)JSON與XML相互轉(zhuǎn)換的簡明教程
Java實現(xiàn)復(fù)雜數(shù)據(jù)結(jié)構(gòu)(如嵌套對象、數(shù)組)在 JSON 與 XML 之間的相互轉(zhuǎn)換,可以使用 Jackson 和 Jackson XML 擴展庫來完成,Jackson 是一個流行的 JSON 處理庫,通過 Jackson 的 XML 擴展庫,可以實現(xiàn) JSON 和 XML 之間的轉(zhuǎn)換,需要的朋友可以參考下2024-08-08
springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter
這篇文章主要介紹了springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter,本文分步驟結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-06-06
springboot post接口接受json時,轉(zhuǎn)換為對象時,屬性都為null的解決
這篇文章主要介紹了springboot post接口接受json時,轉(zhuǎn)換為對象時,屬性都為null的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot掃描自定義的servlet和filter代碼詳解
本文是一篇根據(jù)作者工作經(jīng)歷總結(jié)出來的關(guān)于springboot掃描自定義的servlet和filter代碼詳解的文章,小編覺得非常不錯,這里給大家分享下,和朋友們一起學(xué)習(xí),進步。2017-10-10
SpringSecurity配置HTTPS的實現(xiàn)
本文介紹了SpringBoot項目中配置HTTPS并集成SpringSecurity,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01

