Spring Event 業(yè)務(wù)解耦神器,大大提高可擴(kuò)展性,刷爆了!
一、前言
ApplicationContext 中的事件處理是通過(guò) ApplicationEvent 類(lèi)和 ApplicationListener 接口提供的。如果將實(shí)現(xiàn)了 ApplicationListener 接口的 bean 部署到容器中,則每次將 ApplicationEvent 發(fā)布到ApplicationContext 時(shí),都會(huì)通知到該 bean,這簡(jiǎn)直是典型的觀察者模式。設(shè)計(jì)的初衷就是為了系統(tǒng)業(yè)務(wù)邏輯之間的解耦,提高可擴(kuò)展性以及可維護(hù)性。
Spring 中提供了以下的事件:
二、ApplicationEvent 與 ApplicationListener 應(yīng)用
1.實(shí)現(xiàn)
自定義事件類(lèi),基于 ApplicationEvent 實(shí)現(xiàn)擴(kuò)展:
public class DemoEvent extends ApplicationEvent {
private static final long serialVersionUID = -2753705718295396328L;
private String msg;
public DemoEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
定義 Listener 類(lèi),實(shí)現(xiàn) ApplicationListener接口,并且注入到 IOC 中。等發(fā)布者發(fā)布事件時(shí),都會(huì)通知到這個(gè)bean,從而達(dá)到監(jiān)聽(tīng)的效果。
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent demoEvent) {
String msg = demoEvent.getMsg();
System.out.println("bean-listener 收到了 publisher 發(fā)布的消息: " + msg);
}
}
要發(fā)布上述自定義的 event,需要調(diào)用 ApplicationEventPublisher 的 publishEvent 方法,我們可以定義一個(gè)實(shí)現(xiàn) ApplicationEventPublisherAware 的類(lèi),并注入 IOC來(lái)進(jìn)行調(diào)用:
@Component
public class DemoPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void sendMsg(String msg) {
applicationEventPublisher.publishEvent(new DemoEvent(this, msg));
}
}
客戶(hù)端調(diào)用 publisher:
@RestController
@RequestMapping("/event")
public class DemoClient implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@GetMapping("/publish")
public void publish(){
DemoPublisher bean = applicationContext.getBean(DemoPublisher.class);
bean.sendMsg("發(fā)布者發(fā)送消息......");
}
}
輸出結(jié)果:
bean-listener 收到了 publisher 發(fā)布的消息: 發(fā)布者發(fā)送消息......
2.基于注解
我們可以不用實(shí)現(xiàn) AppplicationListener 接口 ,在方法上使用 @EventListener 注冊(cè)事件。如果你的方法應(yīng)該偵聽(tīng)多個(gè)事件,并不使用任何參數(shù)來(lái)定義,可以在 @EventListener 注解上指定多個(gè)事件。
重寫(xiě) DemoListener 類(lèi)如下:
public class DemoListener {
@EventListener(value = {DemoEvent.class, TestEvent.class})
public void processApplicationEvent(DemoEvent event) {
String msg = event.getMsg();
System.out.println("bean-listener 收到了 publisher 發(fā)布的消息: " + msg);
}
}
3.事件過(guò)濾
如果希望通過(guò)一定的條件對(duì)事件進(jìn)行過(guò)濾,可以使用 @EventListener 的 condition 屬性。以下實(shí)例中只有 event 的 msg 屬性是 my-event 時(shí)才會(huì)進(jìn)行調(diào)用。
@EventListener(value = {DemoEvent.class, TestEvent.class}, condition = "#event.msg == 'my-event'")
public void processApplicationEvent(DemoEvent event) {
String msg = event.getMsg();
System.out.println("bean-listener 收到了 publisher 發(fā)布的消息: " + msg);
}
此時(shí),發(fā)送符合條件的消息,listener 才會(huì)偵聽(tīng)到 publisher 發(fā)布的消息。
bean-listener 收到了 publisher 發(fā)布的消息: my-event
4.異步事件監(jiān)聽(tīng)
前面提到的都是同步處理事件,那如果我們希望某個(gè)特定的偵聽(tīng)器異步去處理事件,如何做?
使用 @Async 注解可以實(shí)現(xiàn)類(lèi)內(nèi)方法的異步調(diào)用,這樣方法在執(zhí)行的時(shí)候,將會(huì)在獨(dú)立的線程中被執(zhí)行,調(diào)用者無(wú)需等待它的完成,即可繼續(xù)其他的操作。
@EventListener
@Async
public void processApplicationEvent(DemoEvent event) {
String msg = event.getMsg();
System.out.println("bean-listener 收到了 publisher 發(fā)布的消息: " + msg);
}
使用異步監(jiān)聽(tīng)時(shí),有兩點(diǎn)需要注意:
- 如果異步事件拋出異常,則不會(huì)將其傳播到調(diào)用方。
- 異步事件監(jiān)聽(tīng)方法無(wú)法通過(guò)返回值來(lái)發(fā)布后續(xù)事件,如果需要作為處理結(jié)果發(fā)布另一個(gè)事件,請(qǐng)插入 ApplicationEventPublisher 以手動(dòng)發(fā)布事件
三、好處及應(yīng)用場(chǎng)景
ApplicationContext 在運(yùn)行期會(huì)自動(dòng)檢測(cè)到所有實(shí)現(xiàn)了 ApplicationListener 的 bean,并將其作為事件接收對(duì)象。當(dāng)我們與 spring 上下文交互觸發(fā) publishEvent 方法時(shí),每個(gè)實(shí)現(xiàn)了 ApplicationListener 的 bean 都會(huì)收到 ApplicationEvent 對(duì)象,每個(gè) ApplicationListener 可以根據(jù)需要只接收自己感興趣的事件。
這樣做有什么好處呢?
在傳統(tǒng)的項(xiàng)目中,各個(gè)業(yè)務(wù)邏輯之間耦合性比較強(qiáng),controller 和 service 間都是關(guān)聯(lián)關(guān)系,然而,使用 ApplicationEvent 監(jiān)聽(tīng) publisher 這種方式,類(lèi)間關(guān)系是什么樣的?我們不如畫(huà)張圖來(lái)看看。
DemoPublisher 和 DemoListener 兩個(gè)類(lèi)間并沒(méi)有直接關(guān)聯(lián),解除了傳統(tǒng)業(yè)務(wù)邏輯兩個(gè)類(lèi)間的關(guān)聯(lián)關(guān)系,將耦合降到最小。這樣在后期更新、維護(hù)時(shí)難度大大降低了。
ApplicationEvent 使用觀察者模式實(shí)現(xiàn),那什么時(shí)候適合使用觀察者模式呢?觀察者模式也叫 發(fā)布-訂閱模式,例如,微博的訂閱,我們訂閱了某些微博賬號(hào),當(dāng)這些賬號(hào)發(fā)布消息時(shí),我們都會(huì)收到通知。
總結(jié)來(lái)說(shuō),定義對(duì)象間的一種一對(duì)多的依賴(lài)關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴(lài)于它的對(duì)象都得到通知并被自動(dòng)更新,從而實(shí)現(xiàn)廣播的效果。
四、源碼閱讀
Spring中的事件機(jī)制流程:
- ApplicationEventPublisher是Spring的事件發(fā)布接口,事件源通過(guò)該接口的pulishEvent方法發(fā)布事件
- ApplicationEventMulticaster就是Spring事件機(jī)制中的事件廣播器,它默認(rèn)提供一個(gè)SimpleApplicationEventMulticaster實(shí)現(xiàn),如果用戶(hù)沒(méi)有自定義廣播器,則使用默認(rèn)的。它通過(guò)父類(lèi)AbstractApplicationEventMulticaster的getApplicationListeners方法從事件注冊(cè)表(事件-監(jiān)聽(tīng)器關(guān)系保存)中獲取事件監(jiān)聽(tīng)器,并且通過(guò)invokeListener方法執(zhí)行監(jiān)聽(tīng)器的具體邏輯
- ApplicationListener就是Spring的事件監(jiān)聽(tīng)器接口,所有的監(jiān)聽(tīng)器都實(shí)現(xiàn)該接口,本圖中列出了典型的幾個(gè)子類(lèi)。其中RestartApplicationListnener在SpringBoot的啟動(dòng)框架中就有使用
- 在Spring中通常是ApplicationContext本身?yè)?dān)任監(jiān)聽(tīng)器注冊(cè)表的角色,在其子類(lèi)AbstractApplicationContext中就聚合了事件廣播器ApplicationEventMulticaster和事件監(jiān)聽(tīng)器ApplicationListnener,并且提供注冊(cè)監(jiān)聽(tīng)器的addApplicationListnener方法
通過(guò)上圖就能較清晰的知道當(dāng)一個(gè)事件源產(chǎn)生事件時(shí),它通過(guò)事件發(fā)布器ApplicationEventPublisher發(fā)布事件,然后事件廣播器ApplicationEventMulticaster會(huì)去事件注冊(cè)表ApplicationContext中找到事件監(jiān)聽(tīng)器ApplicationListnener,并且逐個(gè)執(zhí)行監(jiān)聽(tīng)器的onApplicationEvent方法,從而完成事件監(jiān)聽(tīng)器的邏輯。
來(lái)到ApplicationEventPublisher 的 publishEvent 方法內(nèi)部:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
}
多播事件方法:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 遍歷所有的監(jiān)聽(tīng)者
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
// 異步調(diào)用監(jiān)聽(tīng)器
executor.execute(() -> invokeListener(listener, event));
}
else {
// 同步調(diào)用監(jiān)聽(tīng)器
invokeListener(listener, event);
}
}
}
invokeListener:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
doInvokeListener:
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 這里是事件發(fā)生的地方
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
......
}
}
點(diǎn)擊 ApplicationListener 接口 onApplicationEvent 方法的實(shí)現(xiàn),可以看到我們重寫(xiě)的方法。
五、總結(jié)
Spring 使用反射機(jī)制,獲取了所有繼承 ApplicationListener 接口的監(jiān)聽(tīng)器,在 Spring 初始化時(shí),會(huì)把監(jiān)聽(tīng)器都自動(dòng)注冊(cè)到注冊(cè)表中。
Spring 的事件發(fā)布非常簡(jiǎn)單,我們來(lái)總結(jié)一下:
- 定義一個(gè)繼承 ApplicationEvent 的事件
- 定義一個(gè)實(shí)現(xiàn) ApplicationListener 的監(jiān)聽(tīng)器或者使用 @EventListener 監(jiān)聽(tīng)事件
- 定義一個(gè)發(fā)送者,調(diào)用 ApplicationContext 直接發(fā)布或者使用 ApplicationEventPublisher 來(lái)發(fā)布自定義事件
最后,發(fā)布-訂閱模式可以很好的將業(yè)務(wù)邏輯進(jìn)行解耦(上圖驗(yàn)證過(guò)),大大提高了可維護(hù)性、可擴(kuò)展性。