筆記簡述
本學習筆記主要是介紹Spring中的事件通知是如何實現的,同步和異步事件通知的用法和實現細節以及Spring提供的常見的Event,如果實際開發中需要根據事件推送完成相應的功能,該如何選擇Event
Spring更多可查看Spring 源碼學習
目錄
Spring Event事件通知機制 源碼學習
1、監聽者模式
2、DEMO(同步)
3、Spring實現細節
4、Spring Event
4.1 ContextRefreshedEvent
4.2 ServletRequestHandledEvent
5、異步Pushlish以及DEMO
1、監聽者模式
學習spring的事件通知機制肯定要先了解監聽者模式(監聽者模式和觀察者模式有什么區別?)
監聽者模式包含了一個監聽者Listener與之對應的事件Event,還有一個事件發布者EventPublish,過程就是EventPublish發布一個事件,被監聽者捕獲到,然后執行事件相應的方法
觀察者模式是一對多的模式,一個被觀察者Observable和多個觀察者Observer,被觀察者中存儲了所有的觀察者對象,當被觀察者接收到一個外界的消息,就會遍歷廣播推算消息給所有的觀察者
例如日常生活中的訂閱報紙,報紙老板A,現在小明和老板打招呼說我要訂報紙(這個過程就相當于觀察者的注冊),老板A就會拿出自己的小本本記下小明,下次小王、小芳也是類似的操作,現在老板A就有了三個觀察者了,然后老板會自動的把報紙送到三位的家里,突然有一天小明說不想訂報紙了,老板就在自己的小本本上劃掉小明的名字(觀察者的取消注冊),等到送報紙的時候就不再送到小明家里。
2、DEMO(同步)
事件定義
public class EventDemo extends ApplicationEvent {
private String message;
public EventDemo(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
事件監聽者
@Component
public class EventDemoListern implements ApplicationListener<EventDemo> {
@Override
public void onApplicationEvent(EventDemo event) {
System.out.println("receiver " + event.getMessage());
}
}
事件發布
@Component
public class EventDemoPublish {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publish(String message){
EventDemo demo = new EventDemo(this, message);
applicationEventPublisher.publishEvent(demo);
}
}
最后獲取到EventDemoPublish這個bean,直接publish就可以完成操作了。
3、Spring實現細節
在spring中由于類的細節太多,參數方法也非常的多,不太建議通篇的一個一個看,優先關注我們關注的點,然后打斷點的方式具體了解需要的參數和必備的方法等
在AbstractApplicationContext類
就有publishEvent()方法,先分析下。
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
// 如果是ApplicationEvent對象
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<Object>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
// 否則就包裝為PayloadApplicationEvent時間,并獲取對應的事件類型
}
if (this.earlyApplicationEvents != null) {
//初始化時候的事件容器,默認為null的
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
// 監聽者容器?大概的就這個意思,推送消息給監聽器
}
// 如果當前命名空間還有父親節點,也需要給父親推送該消息
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
// 意味著肯定需要提前初始化這個監聽器容器
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
通過分析,又回到了refresh這個核心函數中
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
// 初始化監聽器容器
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
// 手動實現了名稱為applicationEventMulticaster的bean
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
// 還必須得是ApplicationEventMulticaster類
if (logger.isDebugEnabled()) {
logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 否則就默認自定義一個名稱為SimpleApplicationEventMulticaster的監聽器容器
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
"': using default [" + this.applicationEventMulticaster + "]");
}
}
}
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
// 默認是一個空列表,而不是null
// 注冊監聽器,并且加入到監聽器容器中
protected void registerListeners() {
// Register statically specified listeners first.
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
// 把提前存儲好的監聽器添加到監聽器容器中
}
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
// 獲取類型是ApplicationListener的beanName集合,此處不會去實例化bean
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
// 初始化事件為null
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
這樣就很清楚了,在spring進行refresh的時候就完成了監聽器容器和監聽器的初始化工作(可以很方便的注冊自己需要的監聽器或者自定義的監聽器容器對象),只需要獲取到容器就可以直接publish事件了。
4、Spring Event
前面已經說了監聽器一般和具體的某一個事件綁定的(這點就和觀察者模式非常不一樣了),那么就來看看spring已經幫我們實現了哪些Event,具體如下圖
- ApplicationContextEvent(Context...的抽象類)
- ContextClosedEvent 生命周期關閉
- ContextRefreshedEvent refresh完成
- ContextStartedEvent 生命周期啟動
- ContextStoppedEvent 生命周期停止
- PayloadApplicationEvent
- RequestHandledEvent
- ServletRequestHandledEvent RequestHandledEvent的子類,Spring MVC 請求完成之后推送的事件
下面就ContextRefreshedEvent和ServletRequestHandledEvent具體分析下如何使用以及其實現的原理
4.1 ContextRefreshedEvent
在refresh結束之后推送的一個事件,這個時候大部分bean的實例化已經完成了,并且傳遞了this這個數據,那么如果有需要的話,可以在這個地方實現對Spring上下文數據的統計或者監控,簡直不能爽歪歪,但是個人開發目前還沒遇到具體的使用場景
4.2 ServletRequestHandledEvent
Spring MVC 中的事件,直接跳到DispatcherServlet類
,后來到了FrameworkServlet 抽象類
如果可以推送事件的話,則利用web的上下文推送事件,其中包含了一個請求的基本信息,如果需要定制化統計整個HTTP請求的情況,完全可以通過這個事件推送實現
不過這里有一點需要注意到,還有個publishEvents字段,如果需要使用,需要設置為true,如下圖web.xml配置即可
5、異步Pushlish以及DEMO
異步推送采用的是多線程的方法,具體看SimpleApplicationEventMulticaster類
,也就是上面說的監聽器容器
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 獲取符合事件類型的監聽器集合
Executor executor = getTaskExecutor();
if (executor != null) {
// 如果線程池不為null,則提交一個新任務交由線程池運行
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
// 否則就是同步執行
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
// 錯誤處理器不為null
try {
listener.onApplicationEvent(event);
}
catch (Throwable err) {
errorHandler.handleError(err);
// 當推送消息出現異常,利用錯誤處理器去處理該錯誤
}
}
else {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || msg.startsWith(event.getClass().getName())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
}
- 注入一個線程池可實現異步處理
- 使用errorHandler可以做到最后的事件處理的兜底方案
那么問題來了,如何注入一個線程池呢?往哪里注入呢?
只需要手動實現applicationEventMulticaster的bean,并且利用Set注入的方法注入了一個線程池,線程池也需要實例化,就直接使用了spring自帶的簡單異步任務線程池
從同步改成異步,代碼不需要修改,直接注入這么一個bean即可(當然了XML配置和Config配置均可)