18.spring系列- 事件機制
先看幾個問題
- 為什麼使用事件?
- Spring事件的實現有哪些方式?
- spring事件監聽器的處理是同步還是非同步?
- spring事件監聽器支援自定義順序嗎?
為什麼需要使用事件?
先來看一個業務場景:
產品經理:這兩天你幫我實現一個註冊的功能
我:註冊功能比較簡單,將使用者資訊入庫就可以了,虛擬碼如下:
public void registerUser(UserModel user){
//插入使用者資訊到db,完成註冊
this.insertUser(user);
}
過了幾天,產品經理:註冊成功之後,給使用者傳送一封註冊成功的郵件
我:修改了一下上面註冊程式碼,如下:
public void registerUser(UserModel user){
//插入使用者資訊到db,完成註冊
this.insertUser(user);
//傳送郵件
this.sendEmailToUser(user);
}
由於修改了註冊介面,所以所有呼叫這個方法的地方都需要重新測試一遍
又過了幾天,產品經理:註冊成功之後,給使用者發一下優惠券
我:好的,又調整了一下程式碼:
public void registerUser(UserModel user){
//插入使用者資訊到db,完成註冊
this.insertUser(user);
//傳送郵件
this.sendEmailToUser(user);
//傳送優惠券
this.sendCouponToUser(user);
}
我:測試的兄弟們,辛苦一下大家,註冊介面又修改了,幫忙再過一遍。
過了一段時間,公司效益太好,產品經理:註冊的時候,取消給使用者傳送優惠券的功能。
我:又跑去調整了一下上面程式碼,將傳送優惠券的功能幹掉了,如下:
public void registerUser(UserModel user){
//插入使用者資訊到db,完成註冊
this.insertUser(user);
//傳送郵件
this.sendEmailToUser(user);
}
由於調整了程式碼,而註冊功能又屬於核心業務,所以需要讓測試再次幫忙過一遍,又要麻煩測試來一遍了。
突然有一天,產品經理:註冊介面怎麼這麼慢啊,並且還經常失敗?
我:趕緊跑去檢視了一下執行日誌,發現註冊的時候給使用者傳送郵件不穩定,依賴於第三方郵件伺服器,耗時比較長,並且容易失敗。
產品經理:郵件你可以不發,但是你得確保註冊功能必須可以用啊。
我想了想,將上面程式碼改成了下面這樣,傳送郵件放在了子執行緒中執行:
public void registerUser(UserModel user){
//插入使用者資訊到db,完成註冊
this.insertUser(user);
//傳送郵件,放在子執行緒中執行,郵件的傳送結果對註冊邏輯不會有干擾作用
new Thread(()->{
this.sendEmailToUser(user);
}).start();
}
又過了幾天,產品經理又跑來了說:最近效益不好,需要刺激使用者消費,註冊的時候繼續傳送優惠券。
我:。。。
花了點時間,好好覆盤整理了一下:發現問題不在於產品經理,從業務上來看,產品提的這些需求都是需求合理的,而結果程式碼反覆調整、測試反覆測試,以及一些次要的功能導致註冊介面不穩定,這些問題歸根到底,主要還是我的設計不合理導致的,將註冊功能中的一些次要的功能耦合到註冊的方法中了,並且這些功能可能會經常調整,導致了註冊介面的不穩定性。
其實上面程式碼可以這麼做:
找3個人:註冊器、路人A、路人B。
註冊器:負責將使用者資訊落庫,落庫成功之後,喊一聲:使用者XXX註冊成功了。
路人A和路人B,豎起耳朵,當聽到有人喊:XXX註冊成功 的聲音之後,立即行動做出下面反應:
路人A:負責給XXX傳送一封註冊郵件
路人B:負責給XXX傳送優惠券
我們來看一下:
註冊器只負責將使用者資訊落庫,及廣播一條使用者註冊成功的訊息。
A和B相當於一個監聽者,只負責監聽使用者註冊成功的訊息,當聽到有這個訊息產生的時候,A和B就去做自己的事情。
這裡面註冊器是感知不到A/B存在的,A和B也不用感知註冊器的存在,A/B只用關注是否有人廣播:XXX註冊成功了的訊息,當AB聽到有人廣播註冊成功的訊息,他們才做出反應,其他時間閒著休息。
這種方式就非常好:
當不想給使用者傳送優惠券的時候,只需要將B去掉就行了,此時基本上也不用測試,註冊一下B的程式碼就行了。
若註冊成功之後需要更多業務,比如還需要給使用者增加積分,只需新增一個監聽者C,監聽到註冊成功訊息後,負責給使用者新增積分,此時根本不用去調整註冊的程式碼,開發者和測試人員只需要確保監聽者C中的正確性就可以了。
上面這種模式就是事件模式。
下面我們使用事件模式實現使用者註冊的業務
事件物件 :表示所有事件的父類,內部有個source欄位,表示事件源;我們自定義的事件需要繼承這個類。
/**
* 事件物件
*/
public abstract class AbstractEvent {
//事件源
protected Object source;
public AbstractEvent(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
}
事件監聽器
/**
* 事件監聽器
*
* @param <E> 當前監聽器感興趣的事件型別
*/
public interface EventListener<E extends AbstractEvent> {
/**
* 此方法負責處理事件
*
* @param event 要響應的事件物件
*/
void onEvent(E event);
}
事件廣播器
/**
* 事件廣播器:
* 1.負責事件監聽器的管理(註冊監聽器&移除監聽器,將事件和監聽器關聯起來)
* 2.負責事件的廣播(將事件廣播給所有的監聽器,對該事件感興趣的監聽器會處理該事件)
*/
public interface EventMulticaster {
/**
* 廣播事件給所有的監聽器,對該事件感興趣的監聽器會處理該事件
*
* @param event
*/
void multicastEvent(AbstractEvent event);
/**
* 新增一個事件監聽器(監聽器中包含了監聽器中能夠處理的事件)
*
* @param listener 需要新增監聽器
*/
void addEventListener(EventListener<?> listener);
/**
* 將事件監聽器移除
*
* @param listener 需要移除的監聽器
*/
void removeEventListener(EventListener<?> listener);
}
事件廣播預設實現
/**
* 事件廣播器簡單實現
*/
public class SimpleEventMulticaster implements EventMulticaster {
private Map<Class<?>, List<EventListener>> eventObjectEventListenerMap = new ConcurrentHashMap<>();
@Override
public void multicastEvent(AbstractEvent event) {
List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
if (eventListeners != null) {
for (EventListener eventListener : eventListeners) {
eventListener.onEvent(event);
}
}
}
@Override
public void addEventListener(EventListener<?> listener) {
Class<?> eventType = this.getEventType(listener);
List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
if (eventListeners == null) {
eventListeners = new ArrayList<>();
this.eventObjectEventListenerMap.put(eventType, eventListeners);
}
eventListeners.add(listener);
}
@Override
public void removeEventListener(EventListener<?> listener) {
Class<?> eventType = this.getEventType(listener);
List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
if (eventListeners != null) {
eventListeners.remove(listener);
}
}
/**
* 獲取事件監聽器需要監聽的事件型別
*
* @param listener
* @return
*/
protected Class<?> getEventType(EventListener listener) {
ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
Type eventType = parameterizedType.getActualTypeArguments()[0];
return (Class<?>) eventType;
}
}
上面3個類支撐了整個時間模型,下面我們使用上面三個類來實現註冊的功能,目標是:高內聚低耦合,讓註冊邏輯方便擴充套件。
自定義使用者註冊成功事件類
/**
* 使用者註冊成功事件
*/
public class UserRegisterSuccessEvent extends AbstractEvent {
//使用者名稱
private String userName;
/**
* 建立使用者註冊成功事件物件
*
* @param source 事件源
* @param userName 當前註冊的使用者名稱
*/
public UserRegisterSuccessEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
使用者註冊服務
/**
* 使用者註冊服務
*/
public class UserRegisterService {
//事件釋出者
private EventMulticaster eventMulticaster; //@0
/**
* 註冊使用者
*
* @param userName 使用者名稱
*/
public void registerUser(String userName) {
//使用者註冊(將使用者資訊入庫等操作)
System.out.println(String.format("使用者【%s】註冊成功", userName));
//廣播事件
this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName));
}
public EventMulticaster getEventMulticaster() {
return eventMulticaster;
}
public void setEventMulticaster(EventMulticaster eventMulticaster) {
this.eventMulticaster = eventMulticaster;
}
}
下面我們使用spring來將上面的物件組裝起來
@Configuration
@ComponentScan
public class MainConfig0 {
/**
* 註冊一個bean:事件釋出者
*
* @param eventListeners
* @return
*/
@Bean
@Autowired(required = false)
public EventMulticaster eventMulticaster(List<EventListener> eventListeners) {
EventMulticaster eventPublisher = new SimpleEventMulticaster();
if (eventListeners != null) {
eventListeners.forEach(eventPublisher::addEventListener);
}
return eventPublisher;
}
/**
* 註冊一個bean:使用者註冊服務
*
* @param eventMulticaster
* @return
*/
@Bean
public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) {
UserRegisterService userRegisterService = new UserRegisterService();
userRegisterService.setEventMulticaster(eventMulticaster);
return userRegisterService;
}
}
來個測試用例模擬使用者註冊
@Test
public void test0() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);
//獲取使用者註冊服務
com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService userRegisterService =
context.getBean(com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService.class);
//模擬使用者註冊
userRegisterService.registerUser("spring");
}
執行輸出:
使用者【spring】註冊成功
新增註冊成功傳送郵件功能
/**
* 使用者註冊成功事件監聽器->負責給使用者傳送郵件
*/
@Component
public class SendEmailOnUserRegisterSuccessListener implements EventListener<UserRegisterSuccessEvent> {
@Override
public void onEvent(UserRegisterSuccessEvent event) {
System.out.println(
String.format("給使用者【%s】傳送註冊成功郵件!", event.getUserName()));
}
}
執行結果:
使用者【spring】註冊成功
給使用者【spring】傳送註冊成功郵件!
上面將註冊的主要邏輯(使用者資訊落庫)和次要的業務邏輯(傳送郵件)通過事件的方式解耦了。次要的業務做成了可插拔的方式,比如不想傳送郵件了,只需要將郵件監聽器上面的@Component註釋就可以了,非常方便擴充套件。
spring中實現事件模式
事件相關的幾個類:
- ApplicationEvent:事件物件的父類
- ApplicationListener:事件監聽器介面
- ApplicationEventMulticaster:事件廣播器
- SimpleApplicationEventMulticaster:事件廣播器的簡單實現
硬編碼的方式使用spring事件
步驟一:定義事件
自定義事件,需要繼承ApplicationEvent類
步驟二:定義監聽器
自定義器監聽器,需要實現ApplicationListener介面,並實現onApplicationEvent這個方法,用來處理感興趣的事件
步驟三:建立事件廣播器
建立事件廣播器ApplicationEventMulticaster,這個介面,可以實現這個介面,也可以實現系統給我們提供的SimpleApplicationEventMulticaster介面
步驟四:向廣播器中註冊事件監聽器
將事件監聽器註冊到廣播器中ApplicationEventMulticaster中
步驟五:通過廣播器釋出事件
廣播事件,呼叫ApplicationEventMulticaster#multicastEvent方法廣播事件,此時廣播器中對這個事件感興趣的監聽器會處理這個事件。
我們來個案例:
//訂單事件
public class OrderCreateEvent extends ApplicationEvent {
//訂單編號
private Long orderNo;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public OrderCreateEvent(Object source, Long orderNo) {
super(source);
this.orderNo = orderNo;
}
public Long getOrderNo() {
return orderNo;
}
public void setOrderNo(Long orderNo) {
this.orderNo = orderNo;
}
}
//監聽器
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener<OrderCreateEvent> {
@Override
public void onApplicationEvent(OrderCreateEvent event) {
System.out.println(String.format("訂單【%d】建立成功,給下單人傳送郵件通知!", event.getOrderNo()));
}
}
測試方法:
@Test
public void testEvent1() throws InterruptedException {
//建立事件廣播器
ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
//註冊事件監聽器
applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());
//廣播事件訂單建立事件
applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1l));
}
執行結果:
訂單【1】建立成功,給下單人傳送郵件通知!
Spring為了簡化事件的使用,提供了2種使用方式
- 面向介面的方式
- 面向@EventListener註解的方式
我們先看第一種方式:介面的方式
定義使用者註冊事件:
public class UserRegisterEvent extends ApplicationEvent {
private String userName;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public UserRegisterEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
定義事件監聽器:
@Component
public class SendEmailListener implements ApplicationListener<UserRegisterEvent> {
@Override
public void onApplicationEvent(UserRegisterEvent event) {
System.out.println(String.format("給使用者【%s】傳送註冊成功郵件!", event.getUserName()));
}
}
定義使用者註冊服務:
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
//如果我們想在普通的bean中獲取ApplicationEventPublisher物件,需要實現ApplicationEventPublisherAware介面,利用ApplicationEventPublisher#publishEvent方法進行釋出事件
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 負責使用者註冊及釋出事件的功能
*
* @param userName 使用者名稱
*/
public void registerUser(String userName) {
//使用者註冊(將使用者資訊入庫等操作)
System.out.println(String.format("使用者【%s】註冊成功", userName));
//釋出註冊成功事件
this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
}
}
測試方法:
@Test
public void testEvent2() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
com.spring.event2.UserRegisterService bean = context.getBean(com.spring.event2.UserRegisterService.class);
bean.registerUser("spring event");
}
執行結果:
使用者【spring event】註冊成功
給使用者【spring event】傳送註冊成功郵件!
第二種方式:@EventListener
我們只修改上面案例中的監聽器,改為註解形式:
@Component
public class SendEmailListener{
@EventListener
public void sendMail(UserRegisterEvent event) {
System.out.println(String.format("給使用者【%s】傳送註冊成功郵件!", event.getUserName()));
}
@EventListener
public void sendCompon(UserRegisterEvent event) {
System.out.println(String.format("給使用者【%s】傳送優惠券!", event.getUserName()));
}
}
其他不變,執行結果:
使用者【spring event】註冊成功
給使用者【spring event】傳送註冊成功郵件!
給使用者【spring event】傳送優惠券!
原理:
spring中處理@EventListener註解原始碼位於下面的方法中
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated
EventListenerMethodProcessor實現了SmartInitializingSingleton介面,SmartInitializingSingleton介面中的afterSingletonsInstantiated方法會在所有單例的bean建立完成之後被spring容器呼叫。這塊的內容可以去看一下:spring系列-Bean生命週期
監聽器的排序功能
通過介面實現監聽器的情況
如果自定義的監聽器是通過ApplicationListener介面實現的,那麼指定監聽器的順序有三種方式:
- 實現org.springframework.core.Ordered介面(需要實現一個getOrder方法,返回順序值,值越小,順序越高)
- 實現org.springframework.core.PriorityOrdered介面(PriorityOrdered介面繼承了方式一中的Ordered介面,所以如果你實現PriorityOrdered介面,也需要實現getOrder方法。)
- 類上使用@org.springframework.core.annotation.Order註解
這幾種方式排序規則:
PriorityOrdered#getOrder ASC,Ordered或@Order ASC
監聽器非同步模式
監聽器最終是通過ApplicationEventMulticaster內部的實現來呼叫的,所以我們關注的重點就是這個類,這個類預設有個實現類SimpleApplicationEventMulticaster,這個類是支援監聽器非同步呼叫的,裡面有個欄位:
private Executor taskExecutor;
高併發比較熟悉的朋友對Executor這個介面是比較熟悉的,可以用來非同步執行一些任務。
再來看一下SimpleApplicationEventMulticaster中事件監聽器的呼叫,最終會執行下面這個方法:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
我們看一下容器啟動的時候,初始化操作:
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
大概分析:判斷spring容器中是否有名稱為applicationEventMulticaster的bean,如果有就將其作為事件廣播器,否則建立一個SimpleApplicationEventMulticaster作為廣播器,並將其註冊到spring容器中。
從上面可以得出結論:我們只需要自定義一個型別為SimpleApplicationEventMulticaster名稱為applicationEventMulticaster的bean就可以了,順便給executor設定一個值,就可以實現監聽器非同步執行了。
具體實現:
@ComponentScan
@Configuration
public class MainConfig8 {
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(){
//建立一個事件廣播器
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
//給廣播器提供一個執行緒池,通過這個執行緒池來呼叫事件監聽器
Executor taskExecutor = this.applicationEventMulticasterThreadPool().getObject();
//設定非同步執行器
multicaster.setTaskExecutor(taskExecutor);
return multicaster;
}
@Bean
public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
result.setCorePoolSize(5);
return result;
}
}
其他不變,把列印語句上新增上執行緒名稱,看一下是否是同一個執行緒,執行結果:
執行緒【main】負責使用者【spring event】註冊成功
執行緒【applicationEventMulticasterThreadPool-2】負責給使用者【spring event】傳送優惠券!
執行緒【applicationEventMulticasterThreadPool-1】負責給使用者【spring event】傳送註冊成功郵件!
關於事件,我們就說到這裡。接下來我們介紹bean的迴圈依賴。
相關文章
- Javascript事件模型系列(二)事件的捕獲-冒泡機制及事件委託機制JavaScript事件模型
- Android10_原理機制系列_事件傳遞機制Android事件
- 淺談JS事件機制與React事件機制JS事件React
- DOM事件機制事件
- react事件機制React事件
- redis事件機制Redis事件
- qt事件機制QT事件
- 深入剖析OkHttp系列(五) 來自官方的事件機制HTTP事件
- Redis的事件機制Redis事件
- View事件機制分析View事件
- Android 事件機制Android事件
- C# 事件機制C#事件
- JavaScript執行緒機制與事件機制JavaScript執行緒事件
- JS的事件物件與事件機制JS事件物件
- Redis 事件機制詳解Redis事件
- JS 事件機制 Event LoopJS事件OOP
- 【React深入】React事件機制React事件
- JavaScript事件迴圈機制JavaScript事件
- Qt 事件機制 學習QT事件
- JavaScript 事件迴圈機制JavaScript事件
- View事件分發機制View事件
- Remoting事件機制續REM事件
- Mutexes機制及其等待事件Mutex事件
- jQuery的事件機制,事件物件介紹,外掛機制,多庫共存,each()jQuery事件物件
- 深入理解DOM事件機制事件
- javascript事件迴圈機制EventLoopJavaScript事件OOP
- React原始碼分析 – 事件機制React原始碼事件
- View事件分發機制分析View事件
- Android事件分發機制Android事件
- javascript之事件迴圈機制JavaScript事件
- redis的事件處理機制Redis事件
- js--事件迴圈機制JS事件
- Spring事件機制詳解Spring事件
- Java——事件處理機制概要Java事件
- React原始碼分析 - 事件機制React原始碼事件
- WebSocket的事件觸發機制Web事件
- 知乎 node事件機制 轉載事件
- vue原始碼解析-事件機制Vue原始碼事件