Spring 事件机制

尹吉欢 2020-11-06 01:17:08
spring 事件 机制


Spring 事件机制

参考:

spring 中的事件机制:https://www.cnblogs.com/rickiyang/p/12001524.html

观察者模式 : http://c.biancheng.net/view/1390.html

观察者模式

观察者模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式主要优点如下。

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  2. 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

  1. 观察者模式的主要角色如下。
    1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
    2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
    3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
    4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

以一个小栗子说明,发工资了,老婆孩子都有自己的花费

  1. public class MyObserverDesign {
  2. public static void main(String[] args) {
  3. // 发九月工资了
  4. MyMon myMon = new OctberMon();
  5. myMon.addMyRelative(new MyWife());
  6. myMon.addMyRelative(new MySon());
  7. myMon.addMyRelative(new MyDaughter());
  8. myMon.payOff();
  9. }
  10. // 抽象目标: 我的工资
  11. public static abstract class MyMon {
  12. protected List<MyRelative> myRelatives = new ArrayList<>();
  13. public void addMyRelative(MyRelative myRelative) {
  14. myRelatives.add(myRelative);
  15. }
  16. public void removeMyRelative(MyRelative myRelative) {
  17. myRelatives.remove(myRelative);
  18. }
  19. // 发工资了
  20. public abstract void payOff();
  21. }
  22. // 具体目标:11 月的工资
  23. public static class OctberMon extends MyMon{
  24. @Override
  25. public void payOff() {
  26. System.out.println("宝贝们,发工资了,你们想要什么");
  27. for (MyRelative obs : myRelatives) {
  28. obs.spendMoney();
  29. }
  30. }
  31. }
  32. // 抽象观察者 我的亲人
  33. public interface MyRelative {
  34. // 等着花钱
  35. public void spendMoney();
  36. }
  37. // 具体观察者 我的老婆
  38. public static class MyWife implements MyRelative {
  39. @Override
  40. public void spendMoney() {
  41. System.out.println("老公我们去旅游吧~");
  42. }
  43. }
  44. public static class MySon implements MyRelative {
  45. @Override
  46. public void spendMoney() {
  47. System.out.println("爸爸,我要蜘蛛侠~");
  48. }
  49. }
  50. public static class MyDaughter implements MyRelative {
  51. @Override
  52. public void spendMoney() {
  53. System.out.println("爸爸,我要漂亮娃娃~");
  54. }
  55. }
  56. }

执行结果

  1. 宝贝们,发工资了,你们想要什么
  2. 老公我们去旅游吧~
  3. 爸爸,我要蜘蛛侠~
  4. 爸爸,我要漂亮娃娃~

Java本身对事件的支持

Java中提供了基本的事件处理基类:

  1. EventObject:所有事件状态对象都将从其派生的根类;
  2. EventListener:所有事件侦听器接口必须扩展的标记接口;

同样的将上面的观察者模型场景进行改写

创建发工资的事件继承自 EventObject

  1. /**
  2. * 发工资事件
  3. */
  4. public class PayOffEvent extends EventObject {
  5. /**
  6. * Constructs a prototypical Event.
  7. *
  8. * @param source The object on which the Event initially occurred.
  9. * @throws IllegalArgumentException if source is null.
  10. */
  11. public PayOffEvent(Object source) {
  12. super(source);
  13. }
  14. }

然后创建监听器,继承EventListener,老婆孩子等着用钱呢 -_-

  1. public interface MyRelative extends EventListener { void spendMoney();}

具体监听的实现类1

  1. public class MyWife implements MyRelative {
  2. @Override
  3. public void spendMoney() {
  4. System.out.println("老公我们去旅游吧~");
  5. }
  6. }

具体监听的实现类2

  1. public class MySon implements MyRelative {
  2. @Override
  3. public void spendMoney() {
  4. System.out.println("爸爸,我要蜘蛛侠~");
  5. }
  6. }

source

  1. public class Source {
  2. Set<MyRelative> listeners = new HashSet<>();
  3. public void addStateChangeListener(MyRelative listener) {
  4. listeners.add(listener);
  5. }
  6. public void removeStateChangeListener(MyRelative listener) {
  7. listeners.remove(listener);
  8. }
  9. public void notifyListener(){
  10. for (MyRelative listener : listeners) {
  11. listener.spendMoney();
  12. }
  13. }
  14. }

测试

  1. public static void main(String[] args) {
  2. Source source = new Source();
  3. source.addStateChangeListener(new MyWife());
  4. source.addStateChangeListener(new MySon());
  5. source.notifyListener();
  6. }

搜索了很多关于 java事件 的都和该模式相同,但是个人感觉 没有看到该事件接口起到的具体作用,有点类型方法的直接调用,没有体现事件的传播特性。去掉 两个标记接口 EventListener 都没有影响

Spring中的事件机制

上面讲了这么多都是为了补充前置知识。

在 Spring 容器中通过 ApplicationEvent 类和 ApplicationListener 接口来处理事件,如果某个 bean实现 ApplicationListener接口并被部署到容器中,那么每次对应的 ApplicationEvent 被发布到容器中都会通知该 bean ,这是典型的观察者模式。

Spring 的事件默认是同步的,即调用 publishEvent 方法发布事件后,它会处于阻塞状态,直到 onApplicationEvent 接收到事件并处理返回之后才继续执行下去,这种单线程同步的好处是可以进行事务管理。

定义下单成功事件

  1. /**
  2. * 定义下单成功事件
  3. */
  4. public class OrderSuccessEvent extends ApplicationEvent {
  5. /**
  6. * Create a new {@code ApplicationEvent}.
  7. *
  8. * @param source the object on which the event initially occurred or with
  9. * which the event is associated (never {@code null})
  10. */
  11. public OrderSuccessEvent(Object source) {
  12. super(source);
  13. }
  14. }

定义监听器一 当订单下单成功后 发送短信

  1. @Service
  2. public class SmsListener implements ApplicationListener<OrderSuccessEvent> {
  3. @Override
  4. public void onApplicationEvent(OrderSuccessEvent event) {
  5. sendSms();
  6. }
  7. private void sendSms() {
  8. System.out.println("发送订单成功邮件");
  9. }
  10. }

定义监听器二 当订单下单成功后通知 发车

  1. @Service
  2. public class CarService implements ApplicationListener<OrderSuccessEvent> {
  3. @Override
  4. public void onApplicationEvent(OrderSuccessEvent event) {
  5. sendCarInfo();
  6. }
  7. private void sendCarInfo() {
  8. System.out.println("老王該發車了");
  9. }
  10. }

通过Spring 提供的 applicationContext 进行事件发布

  1. @Service
  2. public class OrderService {
  3. @Autowired
  4. private ApplicationContext applicationContext;
  5. public void order(){
  6. System.out.println("下单完成。。。");
  7. applicationContext.publishEvent(new OrderSuccessEvent("126625874"));
  8. System.out.println("流程结束");
  9. }
  10. }

通过web 方式触发 测试

  1. @RestController
  2. public class OrderController {
  3. @Autowired
  4. private OrderService orderService;
  5. @GetMapping(value = "/test/event")
  6. public void testEvent(){
  7. orderService.order();
  8. }
  9. }

访问后结果

  1. 下单完成。。。
  2. 老王該發車了
  3. 发送订单成功邮件
  4. 流程结束

可以看到 是等待监听器 全部成功后才会 流程结束,是同步执行的,且当其中出现异常时不会继续往下传播

将发车修改为

  1. @Service
  2. public class CarService implements ApplicationListener<OrderSuccessEvent> {
  3. @Override
  4. public void onApplicationEvent(OrderSuccessEvent event) {
  5. sendCarInfo();
  6. }
  7. private void sendCarInfo() {
  8. System.out.println("老王該發車了");
  9. if (1/0 == 1){
  10. }
  11. }
  12. }

异常后不会执行 发送短信的任务

可以通过将任务修改为异步执行,在源码 SimpleApplicationEventMulticaster 类中在执行传播事件任务时会判断 是否给 Executor 线程执行器 若给了就 通过线程池的方式执行,否则同步执行

  1. @Override
  2. public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  3. ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  4. Executor executor = getTaskExecutor();
  5. for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  6. if (executor != null) {
  7. executor.execute(() -> invokeListener(listener, event));
  8. }
  9. else {
  10. invokeListener(listener, event);
  11. }
  12. }
  13. }

所以最简单的方法是通过 自己构建 SimpleApplicationEventMulticaster bean 然后给传入一个执行器来实现监听器的异步执行

  1. @Configuration
  2. public class AsyncEventConfig {
  3. @Bean(name = "applicationEventMulticaster")
  4. public ApplicationEventMulticaster getSimpleApplicationEventMultCaster() {
  5. SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();
  6. simpleApplicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
  7. return simpleApplicationEventMulticaster;
  8. }
  9. }

在配置后 在运行结果

  1. 下单完成。。。
  2. 流程结束
  3. 发送订单成功邮件
  4. 老王該發車了
  5. Exception in thread "SimpleAsyncTaskExecutor-19" java.lang.ArithmeticException: / by zero
  6. at com.sun.event.demo.CarService.sendCarInfo(CarService.java:15)
  7. at com.sun.event.demo.CarService.onApplicationEvent(CarService.java:11)
  8. at com.sun.event.demo.CarService.onApplicationEvent(CarService.java:6)
  9. at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
  10. at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
  11. at org.springframework.context.event.SimpleApplicationEventMulticaster.lambda$multicastEvent$0(SimpleApplicationEventMulticaster.java:136)
  12. at java.lang.Thread.run(Thread.java:745)

可以看到就算老王 生病了对 发短信的 监听是没有影响的并且 主流程不需要等待 发邮件和发车完成

SmartApplicationListener 实现有序监听

在多个监听器之间若多个监听器之间需要保持执行的顺序的场景可以通过实现 ApplicationListener的子类 SmartApplicationListener 来实现

将上面两个 监听器修改为

发车监听器

  1. @Service
  2. public class CarServiceWithOrder implements SmartApplicationListener {
  3. @Override
  4. public void onApplicationEvent(ApplicationEvent event) {
  5. System.out.println("发车了 订单号" + event.getSource());
  6. }
  7. @Override
  8. public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
  9. return eventType == OrderSuccessEvent.class;
  10. }
  11. @Override
  12. public boolean supportsSourceType(final Class<?> sourceType) {
  13. return sourceType == String.class;
  14. }
  15. @Override
  16. public int getOrder() {
  17. return 2;
  18. }
  19. }

发送短信监听器

  1. @Service
  2. public class SmsListenerWithOrder implements SmartApplicationListener {
  3. @Override
  4. public void onApplicationEvent(ApplicationEvent event) {
  5. System.out.println("发送短信 订单号" + event.getSource());
  6. }
  7. @Override
  8. public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
  9. return eventType == OrderSuccessEvent.class;
  10. }
  11. @Override
  12. public boolean supportsSourceType(final Class<?> sourceType) {
  13. return sourceType == String.class;
  14. }
  15. @Override
  16. public int getOrder() {
  17. return 1;
  18. }
  19. }

最终执行结果,order 数越大越先执行

  1. 下单完成。。。
  2. 流程结束
  3. 发车了 订单号126625874
  4. 发送短信 订单号126625874

Spring 事件机制原理

从事件发布的方法开始看起,是在这里出发的事件发布,看看监听器是如何获取到事件的

  1. // 发布 下单成功事件
  2. applicationContext.publishEvent(new OrderSuccessEvent("126625874"));

可以在 AbstractApplicationContext 类看到 具体的 执行 publishEvent 的方法的核心逻辑主要在

  1. getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

中,其中 getApplicationEventMulticaster()方法返回的是 SimpleApplicationEventMulticaster对象 可以直接查看其中的实现多播的方法

  1. @Override
  2. public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  3. // 此处 type 就是 com.sun.event.demo.OrderSuccessEvent 类型的事件
  4. ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  5. // 判断是否有注入 线程池
  6. Executor executor = getTaskExecutor();
  7. // 获取所有的监听器
  8. for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  9. // 若注入有线程池 则放入线程池中异步执行
  10. if (executor != null) {
  11. executor.execute(() -> invokeListener(listener, event));
  12. }
  13. else {
  14. invokeListener(listener, event);
  15. }
  16. }
  17. }

执行listener 的包装方法判断是否有注入 异常处理的 ErrorHandler 当监听异常的处理

  1. protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
  2. ErrorHandler errorHandler = getErrorHandler();
  3. if (errorHandler != null) {
  4. try {
  5. doInvokeListener(listener, event);
  6. }
  7. catch (Throwable err) {
  8. errorHandler.handleError(err);
  9. }
  10. }
  11. else {
  12. doInvokeListener(listener, event);
  13. }
  14. }

最终的 监听执行的方法

  1. private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
  2. try {
  3. listener.onApplicationEvent(event);
  4. }
  5. catch (ClassCastException ex) {
  6. String msg = ex.getMessage();
  7. if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
  8. Log logger = LogFactory.getLog(getClass());
  9. if (logger.isTraceEnabled()) {
  10. logger.trace("Non-matching event type for listener: " + listener, ex);
  11. }
  12. }
  13. else {
  14. throw ex;
  15. }
  16. }
  17. }

由于所有的自定义的监听器都会实现ApplicationListener接口这样就可以通过该方法执行 各个监听器中的具体的逻辑。

在上面有个很重要的方法有漏掉 getApplicationListeners(event, type)这个方法用来查找所有的listener类

  1. protected Collection<ApplicationListener<?>> getApplicationListeners(
  2. ApplicationEvent event, ResolvableType eventType) {
  3. // 获取事件发布时的传入参数
  4. Object source = event.getSource();
  5. Class<?> sourceType = (source != null ? source.getClass() : null);
  6. ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
  7. // Quick check for existing entry on ConcurrentHashMap...
  8. ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
  9. if (retriever != null) {
  10. return retriever.getApplicationListeners();
  11. }
  12. if (this.beanClassLoader == null ||
  13. (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
  14. (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
  15. // this.retrievalMutex 为所有的 spring bean 此处需要加 重量级锁
  16. synchronized (this.retrievalMutex) {
  17. retriever = this.retrieverCache.get(cacheKey);
  18. if (retriever != null) {
  19. return retriever.getApplicationListeners();
  20. }
  21. retriever = new ListenerRetriever(true);
  22. Collection<ApplicationListener<?>> listeners =
  23. retrieveApplicationListeners(eventType, sourceType, retriever);
  24. this.retrieverCache.put(cacheKey, retriever);
  25. return listeners;
  26. }
  27. }
  28. else {
  29. // No ListenerRetriever caching -> no synchronization necessary
  30. return retrieveApplicationListeners(eventType, sourceType, null);
  31. }
  32. }

在进行debug 过程中发现,spring 在启动过程中会发布事件的顺序,也能大致了解spring 的启动过程

  1. org.springframework.boot.context.event.ApplicationStartingEvent--->
  2. org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent -->
  3. org.springframework.boot.context.event.ApplicationContextInitializedEvent -->
  4. org.springframework.boot.context.event.ApplicationPreparedEvent -->
  5. org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent-->
  6. org.springframework.context.event.ContextRefreshedEvent -->
  7. org.springframework.boot.context.event.ApplicationStartedEvent -->
  8. org.springframework.boot.context.event.ApplicationReadyEvent -->
  9. 当请求过来时
  10. com.sun.event.demo.OrderSuccessEvent[source=126625874] -->
  11. org.springframework.web.context.support.ServletRequestHandledEvent -->
版权声明
本文为[尹吉欢]所创,转载请带上原文链接,感谢
http://cxytiandi.com/blog/detail/36510

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云