你的位置:首页 > Java教程

[Java教程]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.ClassPath 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 ClassPathnew ClassPath);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      @Override29     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 @Component13 public class AllAcceptedListener implements ApplicationListener<ApplicationEvent> {14   @Override15  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 @Component13 public class CustomizePublisher implements ApplicationEventPublisherAware {14 15  private ApplicationEventPublisher applicationEventPublisher;16 17  public void publish(MealEvent event) {18    applicationEventPublisher.publishEvent(event);19   }20 21   @Override22  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 @Component12 public class MealListener implements ApplicationListener<MealEvent> {13   @Override14  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 mealContent16   *    吃什么17   * @param mealEnum18   *    早餐还是午餐?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   dinner12 }

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 @Component12 public class TroubleListener implements ApplicationListener<TroubleEvent> {13   @Override14  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 @Override15 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 down26          }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 @Override2 public void start() {3   getLifecycleProcessor().start();4  publishEvent(new ContextStartedEvent(this));5 }

ContextStoppedEvent:当ApplicationContext stop时触发该事件; 

1 @Override2 public void stop() {3   getLifecycleProcessor().stop();4  publishEvent(new ContextStoppedEvent(this));5 }

ApplicationListener 

依赖关系

带你一步步走向源码的世界

从上边打印的线程信息可以知道,spring处理事件通知采用的是当前线程,并没有为为我们启动新的线程,所以,如果需要,你要自己处理线程信息哦,当然也可以设定(如何设置?)!

AbstractApplicationContext

补齐:同一个event,被多个listener监听,先被哪个listener执行是由下边的代码决定的:

如何设置线程池?

回到上边的问题,到底该如何设置线程池呢?

AbstractApplicationEventMulticaster 是private的,并且没有提供写入方法...

实际案例

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