閱讀spring源碼時(shí),看到ApplicationEvent
相關(guān)的代碼覺得熟悉又困惑忙上,深入了解了一下拷呆,發(fā)現(xiàn)原來是spring事件機(jī)制(原諒我之前沒用過……)。
這里在【Spring4揭秘 基礎(chǔ)1】監(jiān)聽器和事件的基礎(chǔ)下進(jìn)行一下擴(kuò)展深入疫粥,感謝這篇博文的作者茬斧,他的spring基礎(chǔ)系列文章讓我在閱讀源碼時(shí),輕松了不少梗逮。
注:源碼部分根據(jù)spring-5.0.7版本分析
設(shè)計(jì)模式
spring事件機(jī)制其實(shí)就是觀察者模式的一種體現(xiàn)项秉。忘記或不熟悉觀察者模式的朋友可以看我前面的總結(jié):Head First 設(shè)計(jì)模式(二)觀察者模式
觀察者模式簡(jiǎn)單可分為兩部分:主題和觀察者。當(dāng)一個(gè)主題改變狀態(tài)時(shí)慷彤,它的所有依賴者都會(huì)收到通知并進(jìn)行自動(dòng)更新娄蔼。
Spring事件機(jī)制簡(jiǎn)單可分為三部分:事件、廣播底哗、觀察者岁诉。 “主題改變狀態(tài)” 這個(gè)動(dòng)作被抽離成了 一個(gè)“事件”,由一個(gè)持有所有觀察者的“廣播容器” 進(jìn)行廣播跋选,“觀察者”們 接收到相應(yīng)事件后進(jìn)行自動(dòng)更新涕癣。
這種設(shè)計(jì)其實(shí)繼承自Java本身的事件機(jī)制:
java.util.EventObject
事件狀態(tài)對(duì)象的基類,它封裝了事件源對(duì)象以及和事件相關(guān)的信息前标。所有java的事件類都需要繼承該類坠韩。java.util.EventListener
觀察者基類,當(dāng)事件源的屬性或狀態(tài)改變的時(shí)候炼列,調(diào)用相應(yīng)觀察者內(nèi)的回調(diào)方法只搁。Source
主題類,java中未定義俭尖,持有所有的觀察者氢惋,當(dāng)主題狀態(tài)發(fā)生改變,產(chǎn)生事件,負(fù)責(zé)向所有觀察者發(fā)布事件
Java的事件機(jī)制這里不敞開講明肮,想了解可看:java 事件機(jī)制
Spring中的事件機(jī)制
Spring的事件機(jī)制相關(guān)的核心類有四個(gè):
-
ApplicationEvent
: Spring中的事件基類菱农,繼承自java.util.EventObject
,創(chuàng)建是需要指定事件源
public abstract class ApplicationEvent extends EventObject {
/**
* 創(chuàng)建一個(gè)事件柿估,需要指定事件源
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
}
-
ApplicationEventPublisher
:發(fā)布事件者循未,調(diào)用廣播發(fā)布事件
public interface ApplicationEventPublisher {
/**發(fā)布事件*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
-
ApplicationEventMulticaster
:廣播,持有觀察者集合秫舌,可向集合內(nèi)的所有觀察者通知事件
public interface ApplicationEventMulticaster {
/**
* 添加監(jiān)聽者(觀察者)
*/
void addApplicationListener(ApplicationListener<?> listener);
/**
* 刪除監(jiān)聽者(觀察者)
*/
void removeApplicationListener(ApplicationListener<?> listener);
/**
* 向所有監(jiān)聽者發(fā)布事件
*/
void multicastEvent(ApplicationEvent event);
}
-
ApplicationListener
:觀察者的妖,接收對(duì)應(yīng)事件后,執(zhí)行邏輯
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* 接收事件后足陨,執(zhí)行相應(yīng)邏輯
*/
void onApplicationEvent(E event);
}
事件發(fā)布者ApplicationEventPublisher
持有廣播嫂粟,而廣播ApplicationEventMulticaster
持有若干觀察者ApplicationListener
。一個(gè)事件ApplicationEvent
可以通過發(fā)布者ApplicationEventPublisher
發(fā)布后墨缘,會(huì)調(diào)用廣播ApplicationEventMulticaster
通知所有觀察者星虹,觀察者ApplicationListener
收到通知后執(zhí)行相關(guān)操作。
下面舉例說明:
當(dāng)一個(gè)用戶注冊(cè)結(jié)束后镊讼,我們想要將這個(gè)事件發(fā)生給短信監(jiān)聽者和郵件監(jiān)聽者宽涌,讓他們向用戶發(fā)送短信和郵件。
public class EventDemo {
public static void main(String[] args) {
//構(gòu)建廣播器
ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
//廣播添加監(jiān)聽器
multicaster.addApplicationListener(new RegisterListener1());
multicaster.addApplicationListener(new RegisterListener2());
//構(gòu)建事件發(fā)布者
MyEventPublicsher eventPublicsher = new MyEventPublicsher();
//事件發(fā)布者增加廣播
eventPublicsher.setEventMulticaster(multicaster);
//構(gòu)建注冊(cè)事件
User user = new User("jack", "18782252509", "jack_email@163.com");
System.out.println("用戶注冊(cè)……");
RegisterEvent registerEvent = new RegisterEvent(user);
//發(fā)布注冊(cè)事件
eventPublicsher.publishEvent(registerEvent);
}
/**
* 用戶實(shí)體類
*/
public static class User{
private String id;
private String name;
private String phone;
private String email;
public User(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
//.....GET AND SET
}
/**
* 自定義注冊(cè)事件
*/
public static class RegisterEvent extends ApplicationEvent {
//事件的構(gòu)造方法中蝶棋,必須制定事件源
public RegisterEvent(User user) {
super(user);
}
public User getUser(){
return (User) getSource();
}
}
/**
* 注冊(cè)事件監(jiān)聽者1-短信監(jiān)聽者(即觀察者),負(fù)責(zé)注冊(cè)后發(fā)生短信
* 注意:實(shí)現(xiàn)接口時(shí)卸亮,在泛形中指定事件類型,則只監(jiān)聽該類型事件玩裙。若不指定兼贸,則默認(rèn)監(jiān)聽所有事件。
*/
public static class RegisterListener1 implements ApplicationListener<RegisterEvent> {
public void onApplicationEvent(RegisterEvent event) {
User user = event.getUser();
System.out.println("用戶:"+ user.getName()+"注冊(cè)結(jié)束吃溅,向手機(jī)"+user.getPhone()+"發(fā)送短信!");
}
}
/**
* 注冊(cè)事件監(jiān)聽者2-郵件監(jiān)聽者(即觀察者)溶诞,負(fù)責(zé)注冊(cè)后發(fā)送郵件
* 注意:實(shí)現(xiàn)接口時(shí),在泛形中指定事件類型罕偎,則只監(jiān)聽該類型事件很澄。若不指定,則默認(rèn)監(jiān)聽所有事件颜及。
*/
public static class RegisterListener2 implements ApplicationListener<RegisterEvent> {
public void onApplicationEvent(RegisterEvent event) {
User user = event.getUser();
System.out.println("用戶:"+ user.getName()+"注冊(cè)結(jié)束,發(fā)生郵件:"+user.getEmail());
}
}
/**
* 事件發(fā)布者蹂楣,持有監(jiān)聽者
*/
public static class MyEventPublicsher implements ApplicationEventPublisher{
//廣播
private ApplicationEventMulticaster eventMulticaster;
public void setEventMulticaster(ApplicationEventMulticaster eventMulticaster) {
this.eventMulticaster = eventMulticaster;
}
//發(fā)布事件
public void publishEvent(Object event) {
eventMulticaster.multicastEvent((ApplicationEvent) event);
}
}
}
輸出:
用戶注冊(cè)后
用戶:jack注冊(cè)結(jié)束俏站,向手機(jī)18782252509發(fā)送短信!
用戶:jack注冊(cè)結(jié)束,發(fā)生郵件:jack_email@163.com
源碼細(xì)節(jié)解析
我們主要分析下廣播的細(xì)節(jié)痊土,以SimpleApplicationEventMulticaster
為例:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
//事件被統(tǒng)一封裝成了ResolvableType肄扎,方便形參入口統(tǒng)一
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//根據(jù)事件類型,通過泛形反射獲取對(duì)應(yīng)的監(jiān)聽者
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//獲取廣播配置的線程池
Executor executor = getTaskExecutor();
//如果有配置線程池,則異步通知事件監(jiān)聽者
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
//沒有配置線程池犯祠,同步通知事件監(jiān)聽者
else {
invokeListener(listener, event);
}
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//執(zhí)行監(jiān)聽者對(duì)應(yīng)邏輯
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
……
}
}
}
我們可以看到spring廣播時(shí)旭等,會(huì)先去判斷有沒有配置線程池,如果配置則使用線程池異步執(zhí)行監(jiān)聽者邏輯衡载,否則同步搔耕。
需要注意的是,我們使用spring事件機(jī)制時(shí)痰娱,默認(rèn)是沒有配置線程池的弃榨,也就是默認(rèn)所有的通知都是同步的,需要手動(dòng)指定線程池才會(huì)開啟同步梨睁。
應(yīng)用
設(shè)計(jì)一個(gè)業(yè)務(wù)場(chǎng)景:當(dāng)一個(gè)用戶完成貸款訂單后鲸睛,我們希望執(zhí)行發(fā)送提醒短信、調(diào)用積分服務(wù)增加積分坡贺、通知風(fēng)控服務(wù)重算風(fēng)控值(后續(xù)操作可能增加)等功能官辈。這種業(yè)務(wù)需求開始很可能寫成同步代碼。
//創(chuàng)建訂單
public void createOrder(Order order){
創(chuàng)建貸款訂單遍坟;
發(fā)送提醒短信钧萍;
調(diào)用積分服務(wù)增加積分;
調(diào)用風(fēng)控服務(wù)推送訂單信息政鼠;
……
返回风瘦;
}
隨著業(yè)務(wù)復(fù)雜度的增加,我們很快發(fā)現(xiàn)createOrder()
這個(gè)方法耦合了太多與創(chuàng)建訂單無(wú)關(guān)的邏輯公般,即影響了原本創(chuàng)建訂單方法的效率万搔,在設(shè)計(jì)上又不符合“開閉原則”。
現(xiàn)在使用spring事件機(jī)制我們來解耦官帘,將與注冊(cè)無(wú)關(guān)的操作改為異步瞬雹。這里直接使用注解式寫法。
- 首先我們修改spring中的廣播刽虹,為它注入我們自定義的線程池酗捌,在spring配置加上:
<!--自定義線程池-->
<bean id="myExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
<!--修改容器中的廣播,注入自定義線程池-->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<property name="taskExecutor" ref="myExecutor" />
</bean>
- 定義一個(gè)創(chuàng)建訂單事件
/**
* 創(chuàng)建訂單完成事件
*/
@Component
public class AfterCreateOrderEvent extends ApplicationEvent {
public AfterCreateOrderEvent(Order order) {
super(order);
}
public Order getOrder(){
return (Order) getSource();
}
}
- 使用事件機(jī)制改變?cè)写a
@Service
public class OrderService {
//直接注入spring事件發(fā)布者
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
/**
* 簡(jiǎn)單的創(chuàng)建訂單方法
*/
public void createOrder(Order order) throws InterruptedException {
System.out.println("創(chuàng)建訂單 order:"+order.getOrderNo()+" 結(jié)束");
//調(diào)用事件發(fā)布者發(fā)布事件
applicationEventPublisher.publishEvent(new AfterCreateOrderEvent(order));
System.out.println("createOrder方法 結(jié)束");
}
//加入@EventListener注解后涌哲,該方法可以看出一個(gè)事件監(jiān)聽者
@EventListener
public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
Order order = afterCreateOrderEvent.getOrder();
Thread.sleep(2000);
System.out.println("調(diào)用短信通知服務(wù):" + order.getPhone());
System.out.println("調(diào)用積分服務(wù)增加貸款積分:"+order.getOrderNo());
}
public static void main(String[] args) throws InterruptedException {
Order order = new Order("N123124124124", "18782202534");
//這里指定自己的spring配置路徑
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config/spring-config.xml");
OrderService orderService = context.getBean(OrderService.class);
orderService.createOrder(order);
}
}
輸出:
創(chuàng)建訂單 order:N123124124124 結(jié)束
createOrder方法 結(jié)束
調(diào)用短信通知服務(wù):18782202534
調(diào)用積分服務(wù)增加貸款積分:N123124124124
自此胖缤,創(chuàng)建訂單與其他操作便實(shí)現(xiàn)了異步和解耦。
另一種異步實(shí)現(xiàn)方式
另外阀圾,也可使用@Async注解來實(shí)現(xiàn)事件的異步調(diào)用
@EventListener
@Async
public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
Order order = afterCreateOrderEvent.getOrder();
Thread.sleep(2000);
System.out.println("調(diào)用短信通知服務(wù):" + order.getPhone());
System.out.println("調(diào)用積分服務(wù)增加貸款積分:"+order.getOrderNo());
}
spring配置加上:
<!--開啟異步調(diào)用哪廓,并指定線程池-->
<task:annotation-driven executor="annotationExecutor" />
<!--線程池-->
<task:executor id="annotationExecutor" pool-size="20"/>
但這種方法有弊端,afterCreateOrder()
方法不能放在同一類(OrderService
)里面初烘。原因是spring的代理機(jī)制涡真。