最近一直忙於業務開發實現,很少有機會做一些技術沉澱,這可能對一個技術工作者來說,未必是一件好事。之前就一直想寫一篇關於spring事件驅動模型相關技術博客,主要還是想給自己做一個技術總結,同時也好和大家做一做分享交流。
好吧,下面直接進入正題:
在說spring事件編程模型之前,還是先看下一個實際開發場景:
這個模型可能大家都很熟悉,一般也就是一個正常訂單業務,可能我下訂單了,然後xxx一通亂操作,最後訂單數據入庫。這個是比較簡單的核心業務了。那麼現在問題來了:
問題一:
訂單下完之後,需要基於訂單信息給客戶發一條短信推送
這時後一般的開發人員很容易想到如下模型,很簡單也很容易實現:
繼續看問題二:
訂單下完之後,需要將訂單信息推送給數據中心,做相關大數據分析,報表等,好繼續加
很容易想到,後續場景越來越多,那麼業務代碼需要做的東西也就越來越多,簡而易見,就是這個service的調用越來越多。這樣功能是可以實現的,但是代碼一點不優美,最主要的額問題是,業務增減或業務擴展,都需要去改我們的核心業務代碼。那有什麼好的方法去做相關改善呢,下面就好看看我們的主角:spring事件驅動模型了
首先可以看下如下業務模型:
在訂單發送結束後,我們可以直接將我們的訂單信息包裝起來通過一個事件發佈出去,最後需要消費的直接通過對應的listener去接收我們的事件,然後做對應相關的業務處理即可。這其實就類似於我們經常使用的mq,event其實就是我們的provider,listener就是我們的一個個consumer。使用這種模型的好處就是,在我們業務有相關拓展的時候,可以不用去改造我們的核心業務代碼,只需要關係我們的listener即可,減少很多的開發測試成本。但是這種同樣也會帶來一些相關問題,比如我們的同步異步問題(下面會講),事務問題(這塊問題比較多,後面會單獨寫篇來介紹基於spring事件驅動模型編程的事務問題)。
我們先由淺入深,先來看一個簡單的demo,先來了解下spring事件驅動模型是怎麼用的。
(1)定義事件,很簡單,繼承ApplicationEvent 實現相關方法即可,message就是我需要發佈的內容,可以根據實際業務自己定義。
public class SpringEvent extends ApplicationEvent {
@Getter
private String message;
/**
* Create a new ContextStartedEvent.
*
* @param source the {@code ApplicationContext} that the event is raised for
* (must not be {@code null})
*/
public SpringEvent(ApplicationContext source, String message) {
super(source);
this.message = message;
}
}
(2)定義監聽,監聽需要實現 ApplicationListener接口,並將我們的事件(SpringEvent)泛型進去,表示我們需要監聽的事件是SpringEvent,該類需要交給spring管理,同事在有多個監聽的情況下,支撐監聽的先後執行順序,@Order(10),數值越大,越先執行。
@Component
@Order(2)
public class SpringListener implements ApplicationListener<SpringEvent> {
@Async
@Override
public void onApplicationEvent(SpringEvent event) {
System.out.println(this.getClass() + String.format("收到事件,消息內容爲[%s]",event.getMessage()));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getClass() + "執行了監聽");
}
}
(3)定義事件源,發佈事件,整個spring只提供了一個唯一的事件源ApplicationContext,就是我們的上下文事件,所有想事件都需要直接或間接實現ApplicationContext接口。然後通過applicationContext.publishEvent去發佈相關的事件
@Service
public class TestService {
@Autowired
private ApplicationContext applicationContext;
void test(String meseage){
applicationContext.publishEvent(new SpringEvent(applicationContext,meseage));
System.out.println("事件發佈完了");
}
}
執行效果如下,我配置了兩個監聽,很明顯監聽都收到了事件,並做了相關操作:
上面我配置的是同步,其實spring事件驅動還可以支持異步,默認是同步執行,支持異步的話,需要配置兩個東西:
(1)配置全局任務調度器和線程池
<context:component-scan base-package="com.baomw"/>
<!-- 任務調度器 -->
<task:scheduler id="scheduler" pool-size="5"/>
<!-- 任務執行器 -->
<task:executor id="payment-status-update-thread" pool-size="10"/>
<!--開啓註解調度支持 @Async @Scheduled-->
<task:annotation-driven executor="payment-status-update-thread" scheduler="scheduler" proxy-target-class="true"/>
(2)在我們的監聽上開啓異步 @Async 即可
那麼問題來了,spring底是怎麼執行的?
首先我們來看下事件驅動模型相關類圖
也比較簡單,其實最核心的就是兩個接口:ApplicationEventPublisher,SmartApplicationListener
但是真正將這兩個關聯起來的就需要靠我們的事件發佈器(我這麼命名的):ApplicationEventMulticaster
他在spring容器啓動的時候初始化,可以參考:
org.springframework.context.support.AbstractApplicationContext#refresh
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
Initialize event multicaster for this context.
可以看到spring 是這麼註釋的。初始化所有的事件發佈器到我們的上下文。
/**
* Name of the ApplicationEventMulticaster bean in the factory.
* If none is supplied, a default SimpleApplicationEventMulticaster is used.
* @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
核心實現如下上,首先會在beanFactory中去getbean,如果在配置信息找到一個beanName爲:applicationEventMulticaster的配置,就會初始化全局applicationEventMulticaster,否則就會:實例化一個:SimpleApplicationEventMulticaster,這個類是spring中ApplicationEventMulticaster的唯一實現。
加載完applicationEventMulticaster之後就是註冊我們的listener
這裏面具體實現就不一一貼代碼了,有興趣的可以自行研究下,其實就是掃描所有的ListenerBean,如下,其實就是將所有的ListenerBean添加到一個HashSet裏面去。
初始化完之後便是具體的發佈執行了,下面來看看具體發佈的源碼:
/**
* Publish the given event to all listeners.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @since 4.2
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
這個是applicationContext發佈事件的核心api,核心在這裏,可以看到其實applicationContext就是通過我們的事件發佈器去發佈事件,前面已經看到了,在sping容器初始化的時候就已經初始化好了全局唯一發布器:SimpleApplicationEventMulticaster,
通過執行:org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)方法來發布發佈並執行事件,
看上面的方法,其實就很好理解了,無非就是獲取當前事件的所有監聽,然後去for循環所有的監聽去依次執行:invokeListener監聽方法而已。
這裏需要注意一個問題,同步異步他其實是先去
Executor executor = getTaskExecutor();
去get一個線程池,如果我們有配置線程池的話這邊就開異步線程去執行監聽,否則就是同步執行了,這樣就解決了相關同步異步的問題。
當然裏面還有很多細節操作,比如怎麼去實現我們order,怎麼去根據註解去判定哪些是需要同步哪些是需要異步的,有興趣的可以自己去研究一下,這邊不做一一擴展了。
上述就是spring的事件驅動模型的一些基本使用及相關底層實現的一些核心知識點了,最後,在說下spring給我們預留的相關擴展事件,先看下官方給出的核心事件:
The following table describes the standard events that Spring provides:
其實這些事件貫穿整個spring生命週期,我們可以用不同的監聽去監聽對應階段的時間拿到全局的applicationContext,然後做相關擴展操作:
以ContextRefreshedEvent爲例:
org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#finishRefresh
根據如上調用鏈,可以看到,在容器初始化完成之後,會發佈一個ContextRefreshedEvent事件。
我這邊監聽了一個啓動刷新結束後的事件,效果如下,當然能拿到全局applicationContext,那麼後續就可以做很多相關操作了。
好了關於spring事件編程模型先講到這,純屬個人理解,不足(錯誤)之處歡迎留言指正!