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)聽方法處理器及默認(rèn)事件監(jiān)聽工廠
事件監(jiān)聽方法處理器及默認(rèn)事件監(jiān)聽工廠的bean定義信息注冊
事件監(jiān)聽方法處理器會在后續(xù)用于處理自定義監(jiān)聽中的@EventListener注解
默認(rèn)事件監(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)); } // 默認(rèn)事件監(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)聽方法處理器及默認(rèn)事件監(jiān)聽工廠的實(shí)例化
refresh方法中,invokeBeanFactoryPostProcessors處理BeanFactoryPostProcessor(EventListenerMethodProcessor實(shí)現(xiàn)了BeanFactoryPostProcessor)
實(shí)例化EventListenerMethodProcessor
調(diào)用EventListenerMethodProcessor的postProcessBeanFactory實(shí)例化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)聽是何時添加的
提前實(shí)例化單例后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 { // 單例實(shí)例化后處理 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) { // 默認(rèn)檢索器中獲取應(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ā)布事件實(shí)際做的事情,找到監(jiān)聽器,過濾出能夠處理這個事件的監(jiān)聽器,然后執(zhí)行監(jiān)聽器中針對這個事件的業(yè)務(wù)邏輯。
3.1監(jiān)聽流程總結(jié)
3.1.1. Spring 容器啟動
- 在 Spring 啟動過程中,
ApplicationContext
被初始化,它作為核心容器,提供了事件發(fā)布和監(jiān)聽的機(jī)制。 - Spring 使用
ApplicationEventPublisher
作為事件發(fā)布的核心接口,事件的發(fā)布與處理都在ApplicationContext
內(nèi)部實(shí)現(xiàn)。
3.1.2. 監(jiān)聽器注冊
在 Spring 中,可以通過以下幾種方式注冊監(jiān)聽器:
- 實(shí)現(xiàn) ApplicationListener 接口:將實(shí)現(xiàn)類作為 Spring Bean 注冊,Spring 會自動將它識別為事件監(jiān)聽器。
- 通過 XML 配置:在 XML 文件中配置
<bean class="com.example.MyEventListener"/>
,將監(jiān)聽器注冊到ApplicationContext
。 - 注解方式:使用@EventListener注解
監(jiān)聽器的作用:每當(dāng)發(fā)布的事件類型與監(jiān)聽器泛型參數(shù)中的事件類型匹配時,監(jiān)聽器的 onApplicationEvent
方法就會被調(diào)用。
3.1.3. 事件發(fā)布
發(fā)布者:Spring 中的任何組件都可以通過 ApplicationEventPublisher
發(fā)布事件。通常,ApplicationContext
本身實(shí)現(xiàn)了 ApplicationEventPublisher
,可以直接調(diào)用 publishEvent()
發(fā)布事件。
事件傳播器:默認(rèn)情況下,Spring 使用 SimpleApplicationEventMulticaster
作為事件傳播器,它負(fù)責(zé)查找符合條件的監(jiān)聽器并將事件分發(fā)給它們。
發(fā)布事件的方法:通過 applicationContext.publishEvent(new CustomEvent(this))
來發(fā)布事件。
3.1.4. 事件廣播給監(jiān)聽器
- 篩選監(jiān)聽器:
SimpleApplicationEventMulticaster
會檢查所有注冊的監(jiān)聽器,篩選出對當(dāng)前事件感興趣的監(jiān)聽器(基于事件類型的匹配)。 - 同步與異步:在 Spring 環(huán)境中,默認(rè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. 事件傳播的擴(kuò)展
在某些場景中,一個事件的監(jiān)聽器可能會發(fā)布新的事件,這會形成事件鏈。Spring 容器會遞歸地將這些新事件廣播給感興趣的監(jiān)聽器。
3.2應(yīng)用場景
3.2.1. 解耦業(yè)務(wù)邏輯
- 場景描述:在業(yè)務(wù)流程中,常常需要在某個操作完成后執(zhí)行附加邏輯,比如用戶注冊后發(fā)送歡迎郵件、推送通知、或更新統(tǒng)計(jì)數(shù)據(jù)。
- 實(shí)現(xiàn)方式:通過監(jiān)聽器監(jiān)聽用戶注冊事件,執(zhí)行后續(xù)的附加操作。這樣,核心業(yè)務(wù)邏輯與附加邏輯可以解耦,各自獨(dú)立管理。
- 示例:用戶注冊成功后觸發(fā)
UserRegistrationEvent
,監(jiān)聽器接收事件后完成發(fā)送郵件或通知的任務(wù)。
3.2.2. 事務(wù)性事件
- 場景描述:在某些情況下,需要確保只有當(dāng)事務(wù)成功提交后,才會發(fā)布事件。比如在訂單創(chuàng)建后,確保庫存減少或通知支付系統(tǒng)。
- 實(shí)現(xiàn)方式:通過 @TransactionalEventListener 監(jiān)聽事務(wù)性事件,確保事件只有在事務(wù)提交成功時才會觸發(fā)。
- 示例:訂單創(chuàng)建完成并且數(shù)據(jù)庫事務(wù)成功提交后,觸發(fā)
OrderCreatedEvent
,通知庫存系統(tǒng)減少庫存。
3.2.3. 異步處理任務(wù)
- 場景描述:對于不需要實(shí)時完成的任務(wù),可以通過異步監(jiān)聽器來解放主線程,避免阻塞。
- 實(shí)現(xiàn)方式:在事件監(jiān)聽器方法上使用 @Async,使其在獨(dú)立線程中執(zhí)行異步任務(wù)。
- 示例:用戶在系統(tǒng)中上傳文件,文件處理邏輯通過事件異步執(zhí)行,以保證上傳接口的快速響應(yīng)。
3.2.4. 應(yīng)用啟動或關(guān)閉事件
- 場景描述:在應(yīng)用啟動或關(guān)閉時,通常需要執(zhí)行一些初始化或清理操作,比如加載配置、檢查依賴服務(wù)、關(guān)閉資源等。
- 實(shí)現(xiàn)方式:通過監(jiān)聽 ApplicationReadyEvent、ContextClosedEvent 等應(yīng)用上下文事件,實(shí)現(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)、資源使用情況、流量變化等。
- 實(shí)現(xiàn)方式:使用自定義事件來捕獲和廣播狀態(tài)變化,監(jiān)聽器實(shí)時響應(yīng)狀態(tài)變化,執(zhí)行對應(yīng)操作。
- 示例:當(dāng)服務(wù)發(fā)現(xiàn)高負(fù)載時,發(fā)布
HighLoadEvent
,監(jiān)聽器響應(yīng)并調(diào)整系統(tǒng)參數(shù)或生成告警。
3.2.6. 領(lǐng)域驅(qū)動設(shè)計(jì)(DDD)中的事件處理
- 場景描述:在領(lǐng)域驅(qū)動設(shè)計(jì)中,事件驅(qū)動架構(gòu)常用于處理不同領(lǐng)域的事件交互,比如訂單模塊的事件會影響到支付、物流等模塊。
- 實(shí)現(xiàn)方式:通過領(lǐng)域事件(如訂單支付事件、庫存更新事件)來實(shí)現(xiàn)模塊間的松耦合通信,避免模塊之間的直接依賴。
- 示例:在電商系統(tǒng)中,用戶下單后觸發(fā)
OrderPlacedEvent
,物流模塊監(jiān)聽該事件并安排發(fā)貨。
3.2.7. 跨服務(wù)通信
- 場景描述:在微服務(wù)架構(gòu)中,服務(wù)之間往往需要基于事件進(jìn)行異步通信,降低耦合度。
- 實(shí)現(xiàn)方式:通過發(fā)布事件到消息中間件(如 Kafka、RabbitMQ),各服務(wù)監(jiān)聽感興趣的事件。
- 示例:支付服務(wù)完成支付后觸發(fā)
PaymentCompletedEvent
,訂單服務(wù)監(jiān)聽該事件并更新訂單狀態(tài)。
3.2.8. 監(jiān)聽?wèi)?yīng)用配置變化
- 場景描述:在應(yīng)用運(yùn)行期間,可能需要動態(tài)刷新配置,比如數(shù)據(jù)庫連接、緩存配置等。
- 實(shí)現(xiàn)方式:通過監(jiān)聽配置中心的配置更新事件,觸發(fā)配置的刷新。
- 示例:當(dāng)配置中心檢測到 Redis 緩存配置更新后觸發(fā)
CacheConfigUpdateEvent
,應(yīng)用的緩存配置自動刷新。
3.2.9. 處理安全或認(rèn)證事件
- 場景描述:在用戶認(rèn)證、權(quán)限驗(yàn)證等過程中,可以發(fā)布事件來處理安全相關(guān)操作。
- 實(shí)現(xiàn)方式:監(jiān)聽認(rèn)證成功、認(rèn)證失敗等事件,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,比如記錄日志、鎖定賬戶。
- 示例:用戶多次登錄失敗后觸發(fā)
AuthenticationFailureEvent
,監(jiān)聽器響應(yīng)后鎖定用戶賬戶并生成告警。
以上就是Spring中如何自定義監(jiān)聽器的詳細(xì)內(nèi)容,更多關(guān)于Spring自定義監(jiān)聽器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot接收與響應(yīng)xml報(bào)文請求的實(shí)現(xiàn)
我們在進(jìn)行接口對接時,會出現(xiàn)報(bào)文形式的信息傳遞,這篇文章主要給大家介紹了關(guān)于SpringBoot接收與響應(yīng)xml報(bào)文請求的相關(guān)資料,需要的朋友可以參考下2023-06-06Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn)
這篇文章主要介紹了Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn),需要的朋友可以參考下2020-11-11java使用http實(shí)現(xiàn)文件下載學(xué)習(xí)示例
這篇文章主要介紹了java使用http實(shí)現(xiàn)文件下載學(xué)習(xí)示例,需要的朋友可以參考下2014-04-04Java并發(fā)系列之CyclicBarrier源碼分析
這篇文章主要為大家詳細(xì)分析了Java并發(fā)系列之CyclicBarrier源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03SpringBoot學(xué)習(xí)篇之@Valid與@Validated的區(qū)別
@Valid是使用Hibernate?validation的時候使用,@Validated是只用Spring?Validator校驗(yàn)機(jī)制使用,下面這篇文章主要給大家介紹了關(guān)于SpringBoot學(xué)習(xí)篇之@Valid與@Validated區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11