閱讀spring源碼時,看到ApplicationEvent
相關的代碼覺得熟悉又困惑,深入了解了一下,發現原來是spring事件機制(原諒我之前沒用過……)。
這里在【Spring4揭秘 基礎1】監聽器和事件的基礎下進行一下擴展深入,感謝這篇博文的作者,他的spring基礎系列文章讓我在閱讀源碼時,輕松了不少。
注:源碼部分根據spring-5.0.7版本分析
設計模式
spring事件機制其實就是觀察者模式的一種體現。忘記或不熟悉觀察者模式的朋友可以看我前面的總結:Head First 設計模式(二)觀察者模式
觀察者模式簡單可分為兩部分:主題和觀察者。當一個主題改變狀態時,它的所有依賴者都會收到通知并進行自動更新。
Spring事件機制簡單可分為三部分:事件、廣播、觀察者。 “主題改變狀態” 這個動作被抽離成了 一個“事件”,由一個持有所有觀察者的“廣播容器” 進行廣播,“觀察者”們 接收到相應事件后進行自動更新。
這種設計其實繼承自Java本身的事件機制:
java.util.EventObject
事件狀態對象的基類,它封裝了事件源對象以及和事件相關的信息。所有java的事件類都需要繼承該類。java.util.EventListener
觀察者基類,當事件源的屬性或狀態改變的時候,調用相應觀察者內的回調方法。Source
主題類,java中未定義,持有所有的觀察者,當主題狀態發生改變,產生事件,負責向所有觀察者發布事件
Java的事件機制這里不敞開講,想了解可看:java 事件機制
Spring中的事件機制
Spring的事件機制相關的核心類有四個:
-
ApplicationEvent
: Spring中的事件基類,繼承自java.util.EventObject
,創建是需要指定事件源
public abstract class ApplicationEvent extends EventObject {
/**
* 創建一個事件,需要指定事件源
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
}
-
ApplicationEventPublisher
:發布事件者,調用廣播發布事件
public interface ApplicationEventPublisher {
/**發布事件*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
-
ApplicationEventMulticaster
:廣播,持有觀察者集合,可向集合內的所有觀察者通知事件
public interface ApplicationEventMulticaster {
/**
* 添加監聽者(觀察者)
*/
void addApplicationListener(ApplicationListener<?> listener);
/**
* 刪除監聽者(觀察者)
*/
void removeApplicationListener(ApplicationListener<?> listener);
/**
* 向所有監聽者發布事件
*/
void multicastEvent(ApplicationEvent event);
}
-
ApplicationListener
:觀察者,接收對應事件后,執行邏輯
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* 接收事件后,執行相應邏輯
*/
void onApplicationEvent(E event);
}
事件發布者ApplicationEventPublisher
持有廣播,而廣播ApplicationEventMulticaster
持有若干觀察者ApplicationListener
。一個事件ApplicationEvent
可以通過發布者ApplicationEventPublisher
發布后,會調用廣播ApplicationEventMulticaster
通知所有觀察者,觀察者ApplicationListener
收到通知后執行相關操作。
下面舉例說明:
當一個用戶注冊結束后,我們想要將這個事件發生給短信監聽者和郵件監聽者,讓他們向用戶發送短信和郵件。
public class EventDemo {
public static void main(String[] args) {
//構建廣播器
ApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
//廣播添加監聽器
multicaster.addApplicationListener(new RegisterListener1());
multicaster.addApplicationListener(new RegisterListener2());
//構建事件發布者
MyEventPublicsher eventPublicsher = new MyEventPublicsher();
//事件發布者增加廣播
eventPublicsher.setEventMulticaster(multicaster);
//構建注冊事件
User user = new User("jack", "18782252509", "jack_email@163.com");
System.out.println("用戶注冊……");
RegisterEvent registerEvent = new RegisterEvent(user);
//發布注冊事件
eventPublicsher.publishEvent(registerEvent);
}
/**
* 用戶實體類
*/
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
}
/**
* 自定義注冊事件
*/
public static class RegisterEvent extends ApplicationEvent {
//事件的構造方法中,必須制定事件源
public RegisterEvent(User user) {
super(user);
}
public User getUser(){
return (User) getSource();
}
}
/**
* 注冊事件監聽者1-短信監聽者(即觀察者),負責注冊后發生短信
* 注意:實現接口時,在泛形中指定事件類型,則只監聽該類型事件。若不指定,則默認監聽所有事件。
*/
public static class RegisterListener1 implements ApplicationListener<RegisterEvent> {
public void onApplicationEvent(RegisterEvent event) {
User user = event.getUser();
System.out.println("用戶:"+ user.getName()+"注冊結束,向手機"+user.getPhone()+"發送短信!");
}
}
/**
* 注冊事件監聽者2-郵件監聽者(即觀察者),負責注冊后發送郵件
* 注意:實現接口時,在泛形中指定事件類型,則只監聽該類型事件。若不指定,則默認監聽所有事件。
*/
public static class RegisterListener2 implements ApplicationListener<RegisterEvent> {
public void onApplicationEvent(RegisterEvent event) {
User user = event.getUser();
System.out.println("用戶:"+ user.getName()+"注冊結束,發生郵件:"+user.getEmail());
}
}
/**
* 事件發布者,持有監聽者
*/
public static class MyEventPublicsher implements ApplicationEventPublisher{
//廣播
private ApplicationEventMulticaster eventMulticaster;
public void setEventMulticaster(ApplicationEventMulticaster eventMulticaster) {
this.eventMulticaster = eventMulticaster;
}
//發布事件
public void publishEvent(Object event) {
eventMulticaster.multicastEvent((ApplicationEvent) event);
}
}
}
輸出:
用戶注冊后
用戶:jack注冊結束,向手機18782252509發送短信!
用戶:jack注冊結束,發生郵件:jack_email@163.com
源碼細節解析
我們主要分析下廣播的細節,以SimpleApplicationEventMulticaster
為例:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
//事件被統一封裝成了ResolvableType,方便形參入口統一
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//根據事件類型,通過泛形反射獲取對應的監聽者
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//獲取廣播配置的線程池
Executor executor = getTaskExecutor();
//如果有配置線程池,則異步通知事件監聽者
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
//沒有配置線程池,同步通知事件監聽者
else {
invokeListener(listener, event);
}
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//執行監聽者對應邏輯
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
……
}
}
}
我們可以看到spring廣播時,會先去判斷有沒有配置線程池,如果配置則使用線程池異步執行監聽者邏輯,否則同步。
需要注意的是,我們使用spring事件機制時,默認是沒有配置線程池的,也就是默認所有的通知都是同步的,需要手動指定線程池才會開啟同步。
應用
設計一個業務場景:當一個用戶完成貸款訂單后,我們希望執行發送提醒短信、調用積分服務增加積分、通知風控服務重算風控值(后續操作可能增加)等功能。這種業務需求開始很可能寫成同步代碼。
//創建訂單
public void createOrder(Order order){
創建貸款訂單;
發送提醒短信;
調用積分服務增加積分;
調用風控服務推送訂單信息;
……
返回;
}
隨著業務復雜度的增加,我們很快發現createOrder()
這個方法耦合了太多與創建訂單無關的邏輯,即影響了原本創建訂單方法的效率,在設計上又不符合“開閉原則”。
現在使用spring事件機制我們來解耦,將與注冊無關的操作改為異步。這里直接使用注解式寫法。
- 首先我們修改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>
- 定義一個創建訂單事件
/**
* 創建訂單完成事件
*/
@Component
public class AfterCreateOrderEvent extends ApplicationEvent {
public AfterCreateOrderEvent(Order order) {
super(order);
}
public Order getOrder(){
return (Order) getSource();
}
}
- 使用事件機制改變原有代碼
@Service
public class OrderService {
//直接注入spring事件發布者
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
/**
* 簡單的創建訂單方法
*/
public void createOrder(Order order) throws InterruptedException {
System.out.println("創建訂單 order:"+order.getOrderNo()+" 結束");
//調用事件發布者發布事件
applicationEventPublisher.publishEvent(new AfterCreateOrderEvent(order));
System.out.println("createOrder方法 結束");
}
//加入@EventListener注解后,該方法可以看出一個事件監聽者
@EventListener
public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
Order order = afterCreateOrderEvent.getOrder();
Thread.sleep(2000);
System.out.println("調用短信通知服務:" + order.getPhone());
System.out.println("調用積分服務增加貸款積分:"+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);
}
}
輸出:
創建訂單 order:N123124124124 結束
createOrder方法 結束
調用短信通知服務:18782202534
調用積分服務增加貸款積分:N123124124124
自此,創建訂單與其他操作便實現了異步和解耦。
另一種異步實現方式
另外,也可使用@Async注解來實現事件的異步調用
@EventListener
@Async
public void afterCreateOrder(AfterCreateOrderEvent afterCreateOrderEvent) throws InterruptedException {
Order order = afterCreateOrderEvent.getOrder();
Thread.sleep(2000);
System.out.println("調用短信通知服務:" + order.getPhone());
System.out.println("調用積分服務增加貸款積分:"+order.getOrderNo());
}
spring配置加上:
<!--開啟異步調用,并指定線程池-->
<task:annotation-driven executor="annotationExecutor" />
<!--線程池-->
<task:executor id="annotationExecutor" pool-size="20"/>
但這種方法有弊端,afterCreateOrder()
方法不能放在同一類(OrderService
)里面。原因是spring的代理機制。