移動(dòng)網(wǎng)站如何做權(quán)重鎮(zhèn)江網(wǎng)站制作公司
Spring事件監(jiān)聽機(jī)制是Spring框架中的一種重要技術(shù),允許組件之間進(jìn)行松耦合通信。通過使用事件監(jiān)聽機(jī)制,應(yīng)用程序的各個(gè)組件可以在其他組件不直接引用的情況下,相互發(fā)送和接受消息。
需求
在技術(shù)派中有這樣一個(gè)需求,當(dāng)發(fā)布文章或者文章下線時(shí),會(huì)發(fā)布一個(gè)事件給SiteMap(站點(diǎn)地圖,幫助搜索引擎有效的抓取和索引網(wǎng)站),SiteMap監(jiān)聽到該事件后會(huì)進(jìn)行更新。
事件監(jiān)聽的本質(zhì)是觀察者模式的應(yīng)用,包括事件、事件監(jiān)聽器、事件發(fā)布器等主要組件。
事件:一個(gè)實(shí)現(xiàn)了ApplicationEvent類的對(duì)象,代表了應(yīng)用程序中某個(gè)特定的事件。我們可以根據(jù)需要?jiǎng)?chuàng)建自定義事件,只要繼承ApplicationEvent類并添相關(guān)的屬性和方法就可以了。
事件監(jiān)聽器:實(shí)現(xiàn)了ApplicationListener<E>接口的對(duì)象,其中E表示事件監(jiān)聽器需要處理的事件類型。監(jiān)聽器可以通過onApplicationEvent(E event)方法處理接受到的事件,另外也可以使用@EventListener注解來簡(jiǎn)化事件監(jiān)聽器的實(shí)現(xiàn),技術(shù)派正是采用的這種方式。
事件發(fā)布器:事件發(fā)布器負(fù)責(zé)將事件發(fā)布給所有關(guān)注該事件的監(jiān)聽器,在Spring中,ApplicationEventPublisher接口定義了事件發(fā)布的基本功能,而ApplicationEventPublisherAware接口允許組件獲取到事件發(fā)布器的引用。Spring的核心容器ApplicationContext實(shí)現(xiàn)了ApplicationEventPublisher接口,因此在Spring應(yīng)用中,通常直接使用ApplicationContext作為事件發(fā)布器,技術(shù)派采用該方式。
實(shí)例
第一步(事件)
創(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;}
}
類上的四個(gè)注解為lombok提供。兩個(gè)字段,
type:枚舉類型(ArticleEventEnum),代表事件的類型。
表示文章上線或者下線。
content:泛型(T),表示事件的內(nèi)容,在本例中,我們會(huì)傳一個(gè)文章的ID。
source:在構(gòu)造方法里賣我們還會(huì)傳一個(gè)Object類型的數(shù)據(jù),表示事件的來源,也就是事件的發(fā)布者。
ApplicationEvent:是Spring Framework框架中用于定義事件的基類。
第二步(發(fā)布事件)
定義SpringUtil工具類,實(shí)現(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);}
通過實(shí)現(xiàn)ApplicationContextAware接口,可以讓這個(gè)類在Spring容器啟動(dòng)時(shí)自動(dòng)獲得ApplicationContext引用。(作為事件發(fā)布器)@Component可以讓該類被Spring容器自動(dòng)實(shí)例化和管理。
自動(dòng)裝配過程是通過Spring得ApplicationContextAwareProcessor類實(shí)現(xiàn)得,它是一個(gè)后置處理器。在Spring容器初始化時(shí),他會(huì)檢查所有得Bean,如果Bean實(shí)現(xiàn)了ApplicationContextAware接口。他會(huì)調(diào)用setApplicationContext方法將ApplicationContext的引用傳遞給Bean。
第三步(用事件)
通過調(diào)用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類中可以看到。
/*** 基于文章的上下線,自動(dòng)更新站點(diǎn)地圖** @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));
}
當(dāng)ArticleMsgEvent類型的事件被發(fā)布時(shí),此方法自動(dòng)被觸發(fā),在該方法中,首先獲得事件的類型(ArticleEventEnum枚舉值),然后根據(jù)事件類型執(zhí)行相應(yīng)的操作,上線時(shí)將文章添加到SiteMap,下線時(shí)從SiteMap中刪除。
測(cè)試
這個(gè)就時(shí)技術(shù)派中的事件監(jiān)聽機(jī)制了。
啟動(dòng)Redis,啟動(dòng)服務(wù)端,啟動(dòng)admin端,在后端找一篇文章下線文章。
就可以在debug模式下看到事件觸發(fā)了。
原理分析
Spring事件監(jiān)聽機(jī)制涉及到s四個(gè)主要的類:
事件對(duì)象:ApplicationEvent
事件監(jiān)聽器:ApplicationLisener,事件監(jiān)聽器,可以通過@EventListener注解定義事件處理方法,而無需實(shí)現(xiàn)ApplicationListener接口
事件發(fā)布者:ApplicationEventPublisher,在Spring中可以通過ApplicationEventPublisherAware接口或使用@Autowired注解來注入ApplicationEventPublisher實(shí)例,當(dāng)事件被發(fā)布時(shí),Spring會(huì)自動(dòng)調(diào)用已注冊(cè)的ApplicationListener實(shí)現(xiàn)類得onApplicationEvent()方法。
事件管理者:ApplicationEventMulticaster,管理監(jiān)聽器和發(fā)布事件,通常由SimpleApplicationEventMulticaster類實(shí)現(xiàn)。他會(huì)遍歷所有已經(jīng)注冊(cè)的監(jiān)聽器,并調(diào)用他們的onApplicationEvent()方法。
ApplicationEvent
ApplicationEvent繼承了EventObject對(duì)象。
來看看ApplicationEvent的子類關(guān)系圖
ApplicationEvent 有一個(gè)重要的子類 ApplicationContextEvent,而ApplicationContextEvent 又有 4 個(gè)重要的子類:
ContextStartedEvent:當(dāng) Spring 容器啟動(dòng)時(shí)觸發(fā)該事件。這意味著所有 Bean 都已加載,并且 ApplicationContext 已初始化。
ContextStoppedEvent:當(dāng) Spring 容器停止時(shí)觸發(fā)該事件。當(dāng)容器關(guān)閉并停止處理請(qǐng)求時(shí),通常會(huì)觸發(fā)此事件。
ContextRefreshedEvent:當(dāng) ApplicationContext 刷新時(shí)觸發(fā)該事件。這表示所有Bean 都已創(chuàng)建,并且已初始化所有單例 Bean(前提是它們?cè)谌萜鞒跏蓟瘯r(shí)需要初始化)
ContextClosedEvent:當(dāng) Spring 容器關(guān)閉時(shí)觸發(fā)該事件。這表示所有 Bean 都已銷塾Spring 容器已清理資源并停止,
ApplicationListener
ApplicationListener繼承EventListener接口,并要求實(shí)現(xiàn)onApplicationEvent(E event)方法。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}
onApplicationEvent(E event)方法:當(dāng)發(fā)布某個(gè)事件時(shí),所有注冊(cè)的ApplicationListener 實(shí)例的 onApplicationEvent 方法都會(huì)被調(diào)用。在這個(gè)方法中,可以編寫處理特定事件的邏輯。此方法接收一個(gè)類型為E的參數(shù),這是 ApplicationEvent 的子類表示觸發(fā)的事件。
當(dāng) Spring 應(yīng)用啟動(dòng)時(shí),Spring 會(huì)掃描所有的 Bean,尋找使用了 @EventListener 注解的方法。一旦找到這樣的方法,Spring 會(huì)為這些方法創(chuàng)建 ApplicationListener 實(shí)例并將其注冊(cè)到 ApplicationEventMulticaster。
ApplicationEventMulticaster
ApplicationEventMulticaster 是一個(gè)接口,負(fù)責(zé)管理監(jiān)聽器和發(fā)布事件,包含了注冊(cè)監(jiān)聽器、移除監(jiān)聽器以及發(fā)布事件的方法。
Spring 容器中通常會(huì)有一個(gè)默認(rèn)的實(shí)現(xiàn),如 SimpleApplicationEventMulticaster,繼承了AbstractApplicationEventMulticaster.
AbstractApplicationEventMulticaster 主要實(shí)現(xiàn)了管理監(jiān)聽器的方法(上面接口的前 5 個(gè)方法),比如說 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);,其內(nèi)部類 DefaultListenerRetriever 里面有兩個(gè)集合,用來記錄維護(hù)事件監(jiān)聽器
這就和設(shè)計(jì)模式中的發(fā)布訂閱模式一樣了,維護(hù)一個(gè) List,用來管理所有的訂閱者,當(dāng)發(fā)布者發(fā)布消息時(shí),遍歷對(duì)應(yīng)的訂閱者列表,執(zhí)行各自的回調(diào) handler。
再來看 SimpleApplicationEventMulticaster 類實(shí)現(xiàn)的廣播事件邏輯
multicastEvent 的主要作用是將給定的 ApplicationEvent 廣播給所有匹配的監(jiān)聽器
首先,通過檢査 eventType 參數(shù)是否為 nul 來確定事件類型。如果 eventType 為null,則使用 resolveDefaultEventType(event)方法從事件對(duì)象本身解析事件類型
獲取 Executor,它是一個(gè)可選的任務(wù)執(zhí)行器,用于在異步執(zhí)行監(jiān)聽器時(shí)調(diào)用。如果沒有配置 Executor,則默認(rèn)為 nul,表示使用同步執(zhí)行。
使用 getApplicationListeners(event,type)方法獲取所有匹配給定事件類型的監(jiān)聽器。
對(duì)于每個(gè)匹配的監(jiān)聽器,檢查是否有 Executor 配置。如果存在 Executor,則使用executor.execute()方法將監(jiān)聽器的調(diào)用封裝到一個(gè)異步任務(wù)中。如果沒有配置Executor,則直接同步調(diào)用監(jiān)聽器。
使用 invokeListener(listener,event)方法調(diào)用監(jiān)聽器的 onApplicationEvent方法,將事件傳遞給監(jiān)聽器。
通過這個(gè)實(shí)現(xiàn),SimpleApplicationEventMulticaster 可以將事件廣播給所有關(guān)心該事件的監(jiān)聽器,同時(shí)支持同步和異步執(zhí)行模式。
最后調(diào)用 istener.onApplicationEvent(event);也就是我們通過實(shí)現(xiàn)接口ApplicationListener 的方式來實(shí)現(xiàn)監(jiān)聽器的 onApplicationEvent 實(shí)現(xiàn)邏輯。
ApplicationEventPublisher
ApplicationEventPublisher 是一個(gè)接口,用于將事件發(fā)布給所有感興趣的監(jiān)聽器。
這個(gè)接口的實(shí)現(xiàn)類通常會(huì)將事件委托給
ApplicationEventMulticaster。在 Spring 中ApplicationContext 通常充當(dāng)事件發(fā)布者,它就實(shí)現(xiàn)了 ApplicationEventPublisher 接口。
ApplicationContext 的 publishEvent 方法的邏輯實(shí)現(xiàn)主要在類AbstractApplicationContext 中:
這段代碼的主要邏輯在這:
這段代碼的主要作用是在 ApplicationContext 初始化時(shí)處理應(yīng)用程序事件的發(fā)布。當(dāng)ApplicationContext 還沒有完全初始化時(shí),例如在refresh()方法中earlyApplicationEvents 列表會(huì)被用來保存早期的事件。在這個(gè)階段ApplicationEventMulticaster 還沒有完全配置好,因此無法直接發(fā)布事件。這些早期的事件將在 ApplicationContext 初始化完成后,ApplicationEventMulticaster 配置好后,通過 finishRefresh()方法中的 publishEvent(new ContextRefreshedEvent(this));發(fā)布。
當(dāng) ApplicationContext 初始化完成后,earlyApplicationEvents 列表將被設(shè)置為 null。此時(shí),事件可以直接通過 getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType)方法發(fā)布給所有匹配的監(jiān)聽器,
這個(gè)機(jī)制確保了在 ApplicationContext 初始化過程中產(chǎn)生的事件不會(huì)丟失,而是在ApplicationContext 初始化完成后被正確地發(fā)布給所有感興趣的監(jiān)聽器。
總結(jié)
這篇內(nèi)容通過源碼的形式講解了 Spring 事件監(jiān)聽機(jī)制及其原理。