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

測試結果

測試成員

  • MealListener :MealEvent                  演員

  • TroubleListener :TroubleEvent         演員

  • AllAcceptedListener                            演員

  • MealEnum                                          道具

  • TestPortal                                           入口

  • CustomizePublisher                           導演

成員代碼

接受全部事件的演員(很負責任啊)

 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的,並且沒有提供寫入方法...

實際案例

用在自己的代碼里就是最好的例子了 ^_^


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM