spring事件通知機制詳解,spring事件詳解
優勢
- 解耦
- 對同一種事件有多種處理方式
- 不干擾主線(main line)
起源
要講spring的事件通知機制,就要先了解一下spring中的這些接口和抽象類:
-
ApplicationEventPublisherAware 接口:用來 publish event
-
ApplicationEvent 抽象類,記錄了source和初始化時間戳:用來定義Event
-
ApplicationListener<E extends ApplicationEvent> :用來監聽事件
構建自己的事件機制案例
測試案例
測試入口

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.support.ClassPathXmlApplicationContext;
4
5 import java.util.concurrent.TimeUnit;
6
7 /**
8 * Created by zhangxiaoguang on 16/1/27 下午11:40.
9 * -----------------------------
10 * Desc:
11 */
12 public class TestPortal {
13 public static void main(String[] args) throws InterruptedException {
14
15 final ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
16
17 String[] definitionNames = applicationContext.getBeanDefinitionNames();
18 System.out.println("==============bean====start=================");
19 for (String definitionName : definitionNames) {
20 System.out.println("bean----:" + definitionName);
21 }
22 System.out.println("==============bean====end=================");
23 System.out.println();
24 final CustomizePublisher customizePublisher = applicationContext.getBean(CustomizePublisher.class);
25
26
27 Thread thread = new Thread(new Runnable() {
28 @Override
29 public void run() {
30 try {
31 System.out.println("開始吃飯:");
32
33 MealEvent lunchEvent = new MealEvent("A吃午飯了", MealEnum.lunch);
34 MealEvent breakfastEvent = new MealEvent("B吃早飯了", MealEnum.breakfast);
35 MealEvent dinnerEvent = new MealEvent("C吃晚飯了", MealEnum.dinner);
36 customizePublisher.publish(lunchEvent);
37 TimeUnit.SECONDS.sleep(1l);
38 customizePublisher.publish(breakfastEvent);
39 TimeUnit.SECONDS.sleep(1l);
40 customizePublisher.publish(dinnerEvent);
41 TimeUnit.SECONDS.sleep(1l);
42
43 System.out.println("他們吃完了!");
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 }
47 }
48 });
49 thread.setName("meal-thread");
50 thread.start();
51
52 System.out.println(Thread.currentThread().getName() + " is waiting for ....");
53 thread.join();
54 System.out.println("Done!!!!!!!!!!!!");
55 }
56 }
TestPortal
測試結果
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationEvent;
4 import org.springframework.context.ApplicationListener;
5 import org.springframework.stereotype.Component;
6
7 /**
8 * Created by zhangxiaoguang on 16/1/27 下午11:27.
9 * -----------------------------
10 * Desc:
11 */
12 @Component
13 public class AllAcceptedListener implements ApplicationListener<ApplicationEvent> {
14 @Override
15 public void onApplicationEvent(ApplicationEvent event) {
16 System.out.println(">>>>>>>>>>>>>>>>event:" + event);
17 }
18 }
AllAcceptedListener
導演負責分發事件

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationEventPublisher;
4 import org.springframework.context.ApplicationEventPublisherAware;
5 import org.springframework.stereotype.Component;
6
7 /**
8 * Created by zhangxiaoguang on 16/1/28 上午1:41.
9 * -----------------------------
10 * Desc:
11 */
12 @Component
13 public class CustomizePublisher implements ApplicationEventPublisherAware {
14
15 private ApplicationEventPublisher applicationEventPublisher;
16
17 public void publish(MealEvent event) {
18 applicationEventPublisher.publishEvent(event);
19 }
20
21 @Override
22 public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
23 this.applicationEventPublisher = applicationEventPublisher;
24 }
25 }
CustomizePublisher
負責處理吃飯事件的演員

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationListener;
4 import org.springframework.stereotype.Component;
5
6 /**
7 * Created by zhangxiaoguang on 16/1/27 下午11:27.
8 * -----------------------------
9 * Desc:
10 */
11 @Component
12 public class MealListener implements ApplicationListener<MealEvent> {
13 @Override
14 public void onApplicationEvent(MealEvent event) {
15 System.out.println(String.format(">>>>>>>>>>>thread:%s,type:%s,event:%s",
16 Thread.currentThread().getName(), event.getMealEnum(), event));
17
18 dispatchEvent(event);
19 }
20
21 private void dispatchEvent(MealEvent event) {
22 switch (event.getMealEnum()) {
23 case breakfast:
24 System.out.println(event.getMealEnum() + " to handle!!!");
25 break;
26 case lunch:
27 System.out.println(event.getMealEnum() + " to handle!!!");
28 break;
29 case dinner:
30 System.out.println(event.getMealEnum() + " to handle!!!");
31 break;
32 default:
33 System.out.println(event.getMealEnum() + " error!!!");
34 break;
35 }
36 }
37 }
MealListener
吃飯消息

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationEvent;
4
5 /**
6 * Created by zhangxiaoguang on 16/1/27 下午11:24.
7 * -----------------------------
8 * Desc:吃飯事件
9 */
10 public class MealEvent extends ApplicationEvent {
11
12 private MealEnum mealEnum;
13
14 /**
15 * @param mealContent
16 * 吃什麼
17 * @param mealEnum
18 * 早餐還是午餐?
19 */
20 public MealEvent(String mealContent, MealEnum mealEnum) {
21 super(mealContent);
22 this.mealEnum = mealEnum;
23 }
24
25 public MealEnum getMealEnum() {
26 return mealEnum;
27 }
28 }
MealEvent
工具

![]()
1 package com.meituan.spring.testcase.listener;
2
3 /**
4 * Created by zhangxiaoguang on 16/1/27 下午11:29.
5 * -----------------------------
6 * Desc:
7 */
8 public enum MealEnum {
9 breakfast,
10 lunch,
11 dinner
12 }
MealEnum
令人厭煩的演員

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationListener;
4 import org.springframework.stereotype.Component;
5
6 /**
7 * Created by zhangxiaoguang on 16/1/27 下午11:27.
8 * -----------------------------
9 * Desc:
10 */
11 @Component
12 public class TroubleListener implements ApplicationListener<TroubleEvent> {
13 @Override
14 public void onApplicationEvent(TroubleEvent event) {
15 System.out.println(">>>>>>>>>>>>>>>>event:" + event);
16 }
17 }
TroubleListener
令人厭煩的事件

![]()
1 package com.meituan.spring.testcase.listener;
2
3 import org.springframework.context.ApplicationEvent;
4
5 /**
6 * Created by zhangxiaoguang on 16/1/27 下午11:24.
7 * -----------------------------
8 * Desc:令人厭煩的事件
9 */
10 public class TroubleEvent extends ApplicationEvent {
11 public TroubleEvent(Object source) {
12 super(source);
13 }
14 }
TroubleEvent
總結
詳細定制 event 類型的,則相關定制的listener會處理對應的消息,其他listener不會管閒事;
制定頂級 event 類型的,ApplicationEvent的,則會處理所有的事件。
ApplicationEvent
依賴關系

ContextEvent事件機制簡介
ContextRefreshedEvent:當整個ApplicationContext容器初始化完畢或者刷新時觸發該事件;

![]()
1 @Override
2 public void refresh() throws BeansException, IllegalStateException {
3 synchronized (this.startupShutdownMonitor) {
4 ......
5
6 try {
7
8 ......
9
10 // Last step: publish corresponding event.
11 finishRefresh();
12 }
13
14 catch (BeansException ex) {
15 ......
16 }
17 }
18 }
19 protected void finishRefresh() {
20 // Initialize lifecycle processor for this context.
21 initLifecycleProcessor();
22
23 // Propagate refresh to lifecycle processor first.
24 getLifecycleProcessor().onRefresh();
25
26 // Publish the final event.
27 publishEvent(new ContextRefreshedEvent(this));
28
29 // Participate in LiveBeansView MBean, if active.
30 LiveBeansView.registerApplicationContext(this);
31 }
View Code
ContextClosedEvent:當ApplicationContext doClose時觸發該事件,這個時候會銷毀所有的單例bean;

![]()
1 @Override
2 public void registerShutdownHook() {
3 if (this.shutdownHook == null) {
4 // No shutdown hook registered yet.
5 this.shutdownHook = new Thread() {
6 @Override
7 public void run() {
8 doClose();
9 }
10 };
11 Runtime.getRuntime().addShutdownHook(this.shutdownHook);
12 }
13 }
14 @Override
15 public void close() {
16 synchronized (this.startupShutdownMonitor) {
17 doClose();
18 // If we registered a JVM shutdown hook, we don't need it anymore now:
19 // We've already explicitly closed the context.
20 if (this.shutdownHook != null) {
21 try {
22 Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
23 }
24 catch (IllegalStateException ex) {
25 // ignore - VM is already shutting down
26 }
27 }
28 }
29 }
30 protected void doClose() {
31 if (this.active.get() && this.closed.compareAndSet(false, true)) {
32 ......
33
34 try {
35 // Publish shutdown event.
36 publishEvent(new ContextClosedEvent(this));
37 }
38 catch (Throwable ex) {
39 logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
40 }
41
42 ......
43 }
44 }
View Code
ContextStartedEvent:當ApplicationContext start時觸發該事件;
1 @Override
2 public void start() {
3 getLifecycleProcessor().start();
4 publishEvent(new ContextStartedEvent(this));
5 }
ContextStoppedEvent:當ApplicationContext stop時觸發該事件;
1 @Override
2 public void stop() {
3 getLifecycleProcessor().stop();
4 publishEvent(new ContextStoppedEvent(this));
5 }
ApplicationListener
依賴關系

帶你一步步走向源碼的世界
從上邊打印的線程信息可以知道,spring處理事件通知采用的是當前線程,並沒有為為我們啟動新的線程,所以,如果需要,你要自己處理線程信息哦,當然也可以設定(如何設置?)!
AbstractApplicationContext












補齊:同一個event,被多個listener監聽,先被哪個listener執行是由下邊的代碼決定的:

如何設置線程池?
回到上邊的問題,到底該如何設置線程池呢?
AbstractApplicationEventMulticaster 是private的,並且沒有提供寫入方法...
實際案例
用在自己的代碼裡就是最好的例子了 ^_^