移動網(wǎng)站如何做權重鎮(zhèn)江網(wǎng)站制作公司
Spring事件監(jiān)聽機制是Spring框架中的一種重要技術,允許組件之間進行松耦合通信。通過使用事件監(jiān)聽機制,應用程序的各個組件可以在其他組件不直接引用的情況下,相互發(fā)送和接受消息。
需求
在技術派中有這樣一個需求,當發(fā)布文章或者文章下線時,會發(fā)布一個事件給SiteMap(站點地圖,幫助搜索引擎有效的抓取和索引網(wǎng)站),SiteMap監(jiān)聽到該事件后會進行更新。
事件監(jiān)聽的本質是觀察者模式的應用,包括事件、事件監(jiān)聽器、事件發(fā)布器等主要組件。
事件:一個實現(xiàn)了ApplicationEvent類的對象,代表了應用程序中某個特定的事件。我們可以根據(jù)需要創(chuàng)建自定義事件,只要繼承ApplicationEvent類并添相關的屬性和方法就可以了。
事件監(jiān)聽器:實現(xiàn)了ApplicationListener<E>接口的對象,其中E表示事件監(jiān)聽器需要處理的事件類型。監(jiān)聽器可以通過onApplicationEvent(E event)方法處理接受到的事件,另外也可以使用@EventListener注解來簡化事件監(jiān)聽器的實現(xiàn),技術派正是采用的這種方式。
事件發(fā)布器:事件發(fā)布器負責將事件發(fā)布給所有關注該事件的監(jiān)聽器,在Spring中,ApplicationEventPublisher接口定義了事件發(fā)布的基本功能,而ApplicationEventPublisherAware接口允許組件獲取到事件發(fā)布器的引用。Spring的核心容器ApplicationContext實現(xiàn)了ApplicationEventPublisher接口,因此在Spring應用中,通常直接使用ApplicationContext作為事件發(fā)布器,技術派采用該方式。
實例
第一步(事件)
創(chuàng)建自定義事件ArticleMsgEvent,繼承ApplicationEvent。
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = true)
public class ArticleMsgEvent<T> extends ApplicationEvent {private ArticleEventEnum type;private T content;public ArticleMsgEvent(Object source, ArticleEventEnum type, T content) {super(source);this.type = type;this.content = content;}
}
類上的四個注解為lombok提供。兩個字段,
type:枚舉類型(ArticleEventEnum),代表事件的類型。
表示文章上線或者下線。
content:泛型(T),表示事件的內容,在本例中,我們會傳一個文章的ID。
source:在構造方法里賣我們還會傳一個Object類型的數(shù)據(jù),表示事件的來源,也就是事件的發(fā)布者。
ApplicationEvent:是Spring Framework框架中用于定義事件的基類。
第二步(發(fā)布事件)
定義SpringUtil工具類,實現(xiàn)了ApplicationContexAware
@Component
public class SpringUtil implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.context = applicationContext;}/*** 發(fā)布事件消息** @param event*/public static void publishEvent(ApplicationEvent event) {context.publishEvent(event);}
通過實現(xiàn)ApplicationContextAware接口,可以讓這個類在Spring容器啟動時自動獲得ApplicationContext引用。(作為事件發(fā)布器)@Component可以讓該類被Spring容器自動實例化和管理。
自動裝配過程是通過Spring得ApplicationContextAwareProcessor類實現(xiàn)得,它是一個后置處理器。在Spring容器初始化時,他會檢查所有得Bean,如果Bean實現(xiàn)了ApplicationContextAware接口。他會調用setApplicationContext方法將ApplicationContext的引用傳遞給Bean。
第三步(用事件)
通過調用SpringUtil.publishEvent()發(fā)布事件。在ArticleSettingServiceImpl類中。
@Override
public void updateArticle(ArticlePostReq req) {
ArticleDO article = articleDao.getById(req.getArticleId());
if (article == null) {return;
}if (StringUtils.isNotBlank(req.getTitle())) {article.setTitle(req.getTitle());
}
article.setShortTitle(req.getShortTitle());ArticleEventEnum operateEvent = null;
if (req.getStatus() != null) {article.setStatus(req.getStatus());if (req.getStatus() == PushStatusEnum.OFFLINE.getCode()) {operateEvent = ArticleEventEnum.OFFLINE;} else if (req.getStatus() == PushStatusEnum.REVIEW.getCode()) {operateEvent = ArticleEventEnum.REVIEW;} else if (req.getStatus() == PushStatusEnum.ONLINE.getCode()) {operateEvent = ArticleEventEnum.ONLINE;}// switch (req.getStatus()){// case 0 :// operateEvent = ArticleEventEnum.OFFLINE;// break;// case 3 :// operateEvent = ArticleEventEnum.REVIEW;// break;// case 2 :// operateEvent = ArticleEventEnum.ONLINE;// break;// default:// break;// }}
articleDao.updateById(article);if (operateEvent != null) {// 發(fā)布文章待審核、上線、下線事件SpringUtil.publishEvent(new ArticleMsgEvent<>(this, operateEvent, article.getId()));
}
}
第四步(監(jiān)聽并處理事件)
通過 @EventListener注解來處理事件,在SitemapServiceImpl類中可以看到。
/*** 基于文章的上下線,自動更新站點地圖** @param event*/
@EventListener(ArticleMsgEvent.class)
public void autoUpdateSiteMap(ArticleMsgEvent<Long> event) {ArticleEventEnum type = event.getType();if (type == ArticleEventEnum.ONLINE) {addArticle(event.getContent());} else if (type == ArticleEventEnum.OFFLINE || type == ArticleEventEnum.DELETE) {rmArticle(event.getContent());}
}public void addArticle(Long articleId) {
RedisClient.hSet(SITE_MAP_CACHE_KEY, String.valueOf(articleId), System.currentTimeMillis());
}public void rmArticle(Long articleId) {RedisClient.hDel(SITE_MAP_CACHE_KEY, String.valueOf(articleId));
}
當ArticleMsgEvent類型的事件被發(fā)布時,此方法自動被觸發(fā),在該方法中,首先獲得事件的類型(ArticleEventEnum枚舉值),然后根據(jù)事件類型執(zhí)行相應的操作,上線時將文章添加到SiteMap,下線時從SiteMap中刪除。
測試
這個就時技術派中的事件監(jiān)聽機制了。
啟動Redis,啟動服務端,啟動admin端,在后端找一篇文章下線文章。
就可以在debug模式下看到事件觸發(fā)了。
原理分析
Spring事件監(jiān)聽機制涉及到s四個主要的類:
事件對象:ApplicationEvent
事件監(jiān)聽器:ApplicationLisener,事件監(jiān)聽器,可以通過@EventListener注解定義事件處理方法,而無需實現(xiàn)ApplicationListener接口
事件發(fā)布者:ApplicationEventPublisher,在Spring中可以通過ApplicationEventPublisherAware接口或使用@Autowired注解來注入ApplicationEventPublisher實例,當事件被發(fā)布時,Spring會自動調用已注冊的ApplicationListener實現(xiàn)類得onApplicationEvent()方法。
事件管理者:ApplicationEventMulticaster,管理監(jiān)聽器和發(fā)布事件,通常由SimpleApplicationEventMulticaster類實現(xiàn)。他會遍歷所有已經(jīng)注冊的監(jiān)聽器,并調用他們的onApplicationEvent()方法。
ApplicationEvent
ApplicationEvent繼承了EventObject對象。
來看看ApplicationEvent的子類關系圖
ApplicationEvent 有一個重要的子類 ApplicationContextEvent,而ApplicationContextEvent 又有 4 個重要的子類:
ContextStartedEvent:當 Spring 容器啟動時觸發(fā)該事件。這意味著所有 Bean 都已加載,并且 ApplicationContext 已初始化。
ContextStoppedEvent:當 Spring 容器停止時觸發(fā)該事件。當容器關閉并停止處理請求時,通常會觸發(fā)此事件。
ContextRefreshedEvent:當 ApplicationContext 刷新時觸發(fā)該事件。這表示所有Bean 都已創(chuàng)建,并且已初始化所有單例 Bean(前提是它們在容器初始化時需要初始化)
ContextClosedEvent:當 Spring 容器關閉時觸發(fā)該事件。這表示所有 Bean 都已銷塾Spring 容器已清理資源并停止,
ApplicationListener
ApplicationListener繼承EventListener接口,并要求實現(xiàn)onApplicationEvent(E event)方法。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}
onApplicationEvent(E event)方法:當發(fā)布某個事件時,所有注冊的ApplicationListener 實例的 onApplicationEvent 方法都會被調用。在這個方法中,可以編寫處理特定事件的邏輯。此方法接收一個類型為E的參數(shù),這是 ApplicationEvent 的子類表示觸發(fā)的事件。
當 Spring 應用啟動時,Spring 會掃描所有的 Bean,尋找使用了 @EventListener 注解的方法。一旦找到這樣的方法,Spring 會為這些方法創(chuàng)建 ApplicationListener 實例并將其注冊到 ApplicationEventMulticaster。
ApplicationEventMulticaster
ApplicationEventMulticaster 是一個接口,負責管理監(jiān)聽器和發(fā)布事件,包含了注冊監(jiān)聽器、移除監(jiān)聽器以及發(fā)布事件的方法。
Spring 容器中通常會有一個默認的實現(xiàn),如 SimpleApplicationEventMulticaster,繼承了AbstractApplicationEventMulticaster.
AbstractApplicationEventMulticaster 主要實現(xiàn)了管理監(jiān)聽器的方法(上面接口的前 5 個方法),比如說 addApplicationListener。
public void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.addApplicationListener(listener);}this.applicationListeners.add(listener);
}
最核心的一句代碼: this.defaultRetriever.applicationListeners.add(listener);,其內部類 DefaultListenerRetriever 里面有兩個集合,用來記錄維護事件監(jiān)聽器
這就和設計模式中的發(fā)布訂閱模式一樣了,維護一個 List,用來管理所有的訂閱者,當發(fā)布者發(fā)布消息時,遍歷對應的訂閱者列表,執(zhí)行各自的回調 handler。
再來看 SimpleApplicationEventMulticaster 類實現(xiàn)的廣播事件邏輯
multicastEvent 的主要作用是將給定的 ApplicationEvent 廣播給所有匹配的監(jiān)聽器
首先,通過檢査 eventType 參數(shù)是否為 nul 來確定事件類型。如果 eventType 為null,則使用 resolveDefaultEventType(event)方法從事件對象本身解析事件類型
獲取 Executor,它是一個可選的任務執(zhí)行器,用于在異步執(zhí)行監(jiān)聽器時調用。如果沒有配置 Executor,則默認為 nul,表示使用同步執(zhí)行。
使用 getApplicationListeners(event,type)方法獲取所有匹配給定事件類型的監(jiān)聽器。
對于每個匹配的監(jiān)聽器,檢查是否有 Executor 配置。如果存在 Executor,則使用executor.execute()方法將監(jiān)聽器的調用封裝到一個異步任務中。如果沒有配置Executor,則直接同步調用監(jiān)聽器。
使用 invokeListener(listener,event)方法調用監(jiān)聽器的 onApplicationEvent方法,將事件傳遞給監(jiān)聽器。
通過這個實現(xiàn),SimpleApplicationEventMulticaster 可以將事件廣播給所有關心該事件的監(jiān)聽器,同時支持同步和異步執(zhí)行模式。
最后調用 istener.onApplicationEvent(event);也就是我們通過實現(xiàn)接口ApplicationListener 的方式來實現(xiàn)監(jiān)聽器的 onApplicationEvent 實現(xiàn)邏輯。
ApplicationEventPublisher
ApplicationEventPublisher 是一個接口,用于將事件發(fā)布給所有感興趣的監(jiān)聽器。
這個接口的實現(xiàn)類通常會將事件委托給
ApplicationEventMulticaster。在 Spring 中ApplicationContext 通常充當事件發(fā)布者,它就實現(xiàn)了 ApplicationEventPublisher 接口。
ApplicationContext 的 publishEvent 方法的邏輯實現(xiàn)主要在類AbstractApplicationContext 中:
這段代碼的主要邏輯在這:
這段代碼的主要作用是在 ApplicationContext 初始化時處理應用程序事件的發(fā)布。當ApplicationContext 還沒有完全初始化時,例如在refresh()方法中earlyApplicationEvents 列表會被用來保存早期的事件。在這個階段ApplicationEventMulticaster 還沒有完全配置好,因此無法直接發(fā)布事件。這些早期的事件將在 ApplicationContext 初始化完成后,ApplicationEventMulticaster 配置好后,通過 finishRefresh()方法中的 publishEvent(new ContextRefreshedEvent(this));發(fā)布。
當 ApplicationContext 初始化完成后,earlyApplicationEvents 列表將被設置為 null。此時,事件可以直接通過 getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType)方法發(fā)布給所有匹配的監(jiān)聽器,
這個機制確保了在 ApplicationContext 初始化過程中產(chǎn)生的事件不會丟失,而是在ApplicationContext 初始化完成后被正確地發(fā)布給所有感興趣的監(jiān)聽器。
總結
這篇內容通過源碼的形式講解了 Spring 事件監(jiān)聽機制及其原理。