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的,并且没有提供写入方法...

实际案例

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

posted @ 2016-02-16 21:53  凌渡冰  阅读(21896)  评论(5编辑  收藏  举报