Spring事件監聽
1.舉例
(1)定義監聽器監聽的對象BaseFetchDataEvent
@Getter
@Setter
@ToString
public class BaseFetchDataEvent extends ApplicationEvent {
/** 測試數據對象 */
private ObjectDto objectDto;
/** 計數器 **/
private CountDownLatch countDownLatch;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public BaseFetchDataEvent(Object source) {
super(source);
}
}
(2)創建一個測試對象實體類
@Data
public class ObjectDto {
/** 主鍵ID*/
private Long id;
/** 任務ID*/
private Long taskId;
/** status*/
private Integer status;
}
(3)創建兩個監聽器,直接監聽BaseFetchDataEvent事件
@Slf4j
@Order(1)
@EasyService
public class OneBaseDataEventListener implements ApplicationListener<BaseFetchDataEvent> {
@Override
public void onApplicationEvent(BaseFetchDataEvent event) {
if (event == null) return;
ObjectDto dto = event.getObjectDto();
CountDownLatch countDownLatch = event.getCountDownLatch();
try {
dto.setStatus(2);
} catch (Exception e) {
log.error("e:{}",e);
}finally {
countDownLatch.countDown();
}
}
}
@Slf4j
@Order(2)
@EasyService
public class TwoBaseDataEventListener implements ApplicationListener<BaseFetchDataEvent> {
@Override
public void onApplicationEvent(BaseFetchDataEvent event) {
if (event == null) return;
ObjectDto dto = event.getObjectDto();
CountDownLatch countDownLatch = event.getCountDownLatch();
try {
dto.setId(1l);
dto.setTaskId(1001l);
} catch (Exception e) {
log.error("e:{}",e);
}finally {
countDownLatch.countDown();
}
}
}
(4)定義測試類:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
@ActiveProfiles("dev")
@WebAppConfiguration
@Slf4j
public class ApplicationListenerTest {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Test
public void listenerTest(){
ObjectDto objectDto = new ObjectDto();
try {
CountDownLatch countDownLatch = new CountDownLatch(2);
BaseFetchDataEvent fetchDataEvent = new BaseFetchDataEvent("基礎數據抓取事件");
fetchDataEvent.setCountDownLatch(countDownLatch);
fetchDataEvent.setObjectDto(objectDto);
applicationEventPublisher.publishEvent(fetchDataEvent);
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(objectDto.toString());
}
}
(5)執行結果
監聽器監聽到
BaseFetchDataEvent
事件,并調用onApplicationEvent
方法
2源碼分析
ApplicationEventPublisher
接口(封裝事件發布功能)提供了一個方法publishEvent
,將事件發送出去,通知應用所有已注冊且匹配的監聽器此ApplicationEvent;通知應用所有已注冊且匹配的監聽器此Event ,如果這個Event不是一個ApplicationEvent
,則其被包裹于PayloadApplicationEvent
[站外圖片上傳中...(image-f7841a-1551080194999)]
核心是:getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
//事件發布委托給ApplicationEventMulticaster
來執行getApplicationEventMulticaster()
方法是獲取所有的監聽器。
然后ApplicationEventMulticaster
的multicastEvent
方法的實現在SimpleApplicationEventMulticaster
類中:
獲取event所有的監聽事件,然后遍歷執行監聽器的onApplicationEvent方法,可知此方法是核心方法,是真正調用監聽器的地方;
從下面代碼可以看到,找到已注冊的ApplicationListener
,逐個調用invokeListener
方法,將ApplicationListener
和事件作為入參傳進去就完成了廣播;
[站外圖片上傳中...(image-368b29-1551080194999)]
最終調用invokelistener
,執行onApplicationEvent(event)
;invokeListener
方法:,ApplicationListener
是代表監聽的接口,只要調用這個接口的方法并且將event
作為入參傳進去,那么每個監聽器就可以按需要自己來處理這條廣播消息了,
[站外圖片上傳中...(image-9f76b5-1551080194999)]
如果多線程同時發廣播,會不會有線程同步的問題?
唯一有可能出現問題的地方在:multicastEvent方法獲取ApplicationListener
的時候可能出現同步問題,看代碼:
[站外圖片上傳中...(image-40aed3-1551080194999)]
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
//緩存的key有兩個維度:消息來源+消息類型(關于消息來源可見ApplicationEvent構造方法的入參)
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// retrieverCache是ConcurrentHashMap對象,所以是線程安全的,
// ListenerRetriever中有個監聽器的集合,并有些簡單的邏輯封裝,
//調用它的getApplicationListeners方法返回的監聽類集合是排好序的(order注解排序)
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
//如果retrieverCache中找到對應的監聽器集合,就立即返回了
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
//如果retrieverCache中沒有數據,就在此查出數據并放入緩存,
//先加鎖
synchronized (this.retrievalMutex) {
//雙重判斷的第二重,避免自己在BLOCK的時候其他線程已經將數據放入緩存了
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
//新建一個ListenerRetriever對象
retriever = new ListenerRetriever(true);
//retrieveApplicationListeners方法復制找出某個消息類型加來源類型對應的所有監聽器
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
//存入retrieverCache
this.retrieverCache.put(cacheKey, retriever);
//返回結果
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
在廣播消息的時刻,如果某個類型的消息在緩存中找不到對應的監聽器集合,就調用retrieveApplicationListeners方法去找出符合條件的所有監聽器,然后放入這個集合
3 如何具備消息發送能力
spring容器初始化的時候會對實現了Aware接口的bean做相關的特殊處理,其中就包含ApplicationEventPublisherAware
這個與廣播發送相關的接口
在spring容器初始化的時候,AbstractApplicationContext
類的prepareBeanFactory
方法中為所有bean準備了一個后置處理器ApplicationListenerDetector
,來看看它的postProcessAfterInitialization
方法的代碼,也就是bean在實例化之后要做的事情:
[站外圖片上傳中...(image-c0dd8b-1551080194999)]
核心的一句:
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
此代碼注冊監聽器,其實就是保存在成員變量applicationEventMulticaster
的成員變量defaultRetriever
的集合applicationListeners
中
即:當前bean實現了ApplicationListener
接口,就會調用this.applicationContext.addApplicationListener
方法將當前bean注冊到applicationContext
的監聽器集合中,后面有廣播就直接找到這些監聽器,調用每個監聽器的onApplicationEvent
方法;
自定義的消息監聽器可以指定消息類型,所有的廣播消息中,這個監聽器只會收到自己指定的消息類型的廣播,spring是如何做到這一點的?
4 如何做到只接收指定類型的
自定義監聽器只接收指定類型的消息,以下兩種方案都可以實現:
1.注冊監聽器的時候,將監聽器和消息類型綁定; 2.廣播的時候,按照這條消息的類型去找指定了該類型的監聽器,但不可能每條廣播都去所有監聽器里面找一遍,應該是說廣播的時候會觸發一次監聽器和消息的類型綁定;
spring如何處理?
先看注冊監聽器的代碼
按照之前的分析,注冊監聽發生在后置處理器ApplicationListenerDetector
中,看看this.applicationContext.addApplicationListener
這一行代碼的內部邏輯:
[站外圖片上傳中...(image-f3b4e2-1551080194999)]
繼續往下debug:
把監聽器加入集合defaultRetriever.applicationListeners
中,這是個LinkedHashSet
實例
this.defaultRetriever.applicationListeners.add(listener);
注冊監聽器,其實就是把ApplicationListener
的實現類放入一個LinkedHashSet
的集合,此處沒有任何與消息類型相關的操作,因此,監聽器注冊的時候并沒有將消息類型和監聽器綁定
去看廣播消息的代碼
來到SimpleApplicationEventMulticaster
的multicastEvent
方法
可以看到方法
getApplicationListeners(event, type)
,包含了listener
的type
;即,在發送消息的時候根據類型去找所有對應的監聽器;
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
//緩存的key有兩個維度:消息來源+消息類型(關于消息來源可見ApplicationEvent構造方法的入參)
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// retrieverCache是ConcurrentHashMap對象,所以是線程安全的,
// ListenerRetriever中有個監聽器的集合,并有些簡單的邏輯封裝,調用它的getApplicationListeners方法返回的監聽類集合是排好序的(order注解排序)
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
//如果retrieverCache中找到對應的監聽器集合,就立即返回了
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
//如果retrieverCache中沒有數據,就在此查出數據并放入緩存,
//先加鎖
synchronized (this.retrievalMutex) {
//雙重判斷的第二重,避免自己在BLOCK的時候其他線程已經將數據放入緩存了
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
//新建一個ListenerRetriever對象
retriever = new ListenerRetriever(true);
//retrieveApplicationListeners方法復制找出某個消息類型加來源類型對應的所有監聽器
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
//存入retrieverCache
this.retrieverCache.put(cacheKey, retriever);
//返回結果
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
在廣播消息的時刻,如果某個類型的消息在緩存中找不到對應的監聽器集合,就調用retrieveApplicationListeners
方法去找出符合條件的所有監聽器,然后放入這個集合。跟蹤getApplicationListeners
方法,了解如何獲取事件所有的監聽器
getApplicationListeners
:
代碼中可以看到,首先將listener的所有名字生成一個list,從新遍歷這個list,獲取bean對象,生成bean的一個list,然后調用AnnotationAwareOrderComparator.sort(allListeners);
對list中的bean進行排序
AnnotationAwareOrderComparator
是OrderComparator
的子類,用來支持Spring的Ordered類、@Order注解和@Priority注解
。