热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringApplicationListener分析

Spring的核心是ApplicationContext,它负责管理Bean的完整生命周期;当加载Bean时,ApplicationContext发布某些类型的事件;例如,当上

  Spring 的核心是 ApplicationContext,它负责管理 Bean的完整生命周期;当加载 Bean 时,ApplicationContext 发布某些类型的事件;例如,当上下文启动时,ContextStartedEvent 发布消息,当上下文停止时,ContextStoppedEvent 发布消息;

  通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件;如果一个 Bean 实现 ApplicationListener 接口,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 Bean实例会被通知;类似MQ的发布订阅;

   

  ApplicationEvent UML图如下:

  

 




  • Spring常用事件

































序号Spring 事件
1ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布;这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生;容器刷新完成(所有bean都完全创建)会发布这个事件;
2ContextStartedEvent:当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布;
3ContextStoppedEvent:当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件;
4ContextClosedEvent:当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布;
5RequestHandledEvent:在Web应用中,当一个http请求结束触发该事件

 




  • org.springframework.context.support.AbstractApplicationContext#refresh



  

  AbstractApplicationContext#initApplicationEventMulticaster 用于初始化事件多播器;

  AbstractApplicationContext#registerListeners 用于将事件监听器注册到多播器;

 




  • AbstractApplicationContext#initApplicationEventMulticaster



  ApplicationEventMulticaster该接口定义了具体事件监听器的注册管理以及事件发布的方法 ;

  

 

  

  ApplicationEventMulticaster有一抽象实现类AbstractApplicationEventMulticaster,它实现了事件监听器的管理 功能;出于灵活性和扩展性考虑,事件的发布功能则委托给了其子类,即ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

  SimpleApplicationEventMulticaster 是 Spring 提 供 的 AbstractApplicationEventMulticaster的一个子类实现,添加了事件发布功能的实现;

 




  • AbstractApplicationContext#registerListeners



  

  这里画了三个大框,分别做不同的事情;





    • 第一个大框,里面处理的是将容器中ApplicationListener的实现类注册到多播器中;

    • 第二大个框,里面处理的是将自定义的ApplicationListenr的实现类注册到多播器中;



      DefaultListableBeanFactory#doGetBeanNamesForType

      自定义ApplicationListener的实现类最终会在这里组装到List集合;

      里面调用的AbstractBeanFactory#isTypeMatch,用于判断要查询的beanName的实例与该匹配的类型是否一致;

  



  •  


    • 第三个大框,earlyApplicationEvents是早期事件,而早期事件是指多播器未创建完成前的事件,也就AbstractApplicationContext#initApplicationEventMulticaster方法执行前,将没有多播器则将之前的事件保存,之后调用getApplicationEventMulticaster().multicastEvent(earlyEvent)方法发布事件;


 




  • SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)



  

  getTaskExecutor(),多播器中是否支持线程池异步发送事件;

 




  • SimpleApplicationEventMulticaster#invokeListener



  调用SimpleApplicationEventMulticaster#doInvokeListener

  SimpleApplicationEventMulticaster#doInvokeListener

  

  listener.onApplicationEvent(event),调用对于listener的onApplicationEvent事件;

  Spring的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处理,并不适合分布式、多进程、多容器之间的事件通知 ;

 

  可以通过如下两种方式为我们的业务对象注入ApplicationEventPublisher的依赖



  • 使用ApplicationEventPublisherAware接口;在ApplicationContext类型的容器启动时,会自动识别该类型的bean定义并将ApplicationContext容器本身作为ApplicationEventPublisher注入当前对象,而ApplicationContext容器本身就是一个ApplicationEventPublisher;



  • 使用ApplicationContextAware接口; ApplicationContext本身就是一个ApplicationEventPublisher,通过ApplicationContextAware几乎达到第一种方式相同的效果;



 




  • ApplicationListener,ApplicationEvent,ApplicationEventMulticaster,ApplicationEventPublisher类关系图



  

 

  Spring的事件监听是基于观察者模式的,有三部分组成:





    • 事件(ApplicationEvent):负责对应相应监听器,事件源发生某事件是特定事件监听器被触发的原因;

    • 监听器(ApplicationListener):对应于观察者模式中的观察者,监听器监听特定的事件,并在内部定义了事件后发生相应的逻辑;

    • 事件发布器(ApplicationEventMulticaster):对应于观察者模式中的被观察者/主题,负责通知观察者对外提供发布事件和增删事件监听器的接口,维护事件和监听器的映射关系,并在事件发生时负责通知相关的监听器;



 

  Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者两个角色外,还有一个EventMultiCaster(事件多播器)的角色负责把事件转发给监听器,在代码中体现:发布者调用applicationEventPublisher.publishEvent(msg); 将事件发送给了EventMultiCaster,之后由EventMultiCaster注册所有的Listener,然后根据事件类型决定转发给哪个Listener

 

  测试代码如下:

  配置类


public class DemoApplicationListener1 implements ApplicationListener {
private final static Logger logger = LoggerFactory
.getLogger(DemoApplicationListener1. class );
@Override
public void onApplicationEvent(ApplicationEvent event) {
logger.info( "接收到一个事件:" + event);
}
}

  

  测试类



查看代码

 @Test
public void listenerTest1() {
AnnotationConfigApplicationContext cOntext= new AnnotationConfigApplicationContext(
ListenerConfig1. class );
context.publishEvent( new ApplicationEvent( "发送测试消息" ) {
});
try {
TimeUnit.SECONDS.sleep( 5 );
}
catch (InterruptedException e) {
e.printStackTrace();
}
context.close();
}

  运行结果如下:

  从日志可以看出,事件监听处理是在主线程,上面说到的多播器可以支持线程池异步发送事件,在配置类直接的@Bean一个线程池对象是行不通的,这里的对象是直接new出来的,它不能够直接给Spring管理;多播器要支持异步具体有下面两种方式;

  参考:[https://docs.spring.io/spring-framework/docs/5.2.4.RELEASE/spring-framework-reference/core.html#context-functionality-events]

 



  • 方式一

  在AbstractApplicationContext#initApplicationEventMulticaster会判断IOC容器中是否存在beanName为APPLICATION_EVENT_MULTICASTER_BEAN_NAME的多播器对象,即applicationEventMulticaster,因此可以自定义一个多播器并将它的beanName设置为applicationEventMulticaster

  自定义的事件多播器



查看代码

 @Component ( "applicationEventMulticaster" )
public class DemoEventMulticaster extends SimpleApplicationEventMulticaster {
public DemoEventMulticaster() {
/**
* @See org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster()
*/
setTaskExecutor(Executors.newCachedThreadPool());
}
}

  

  上面的配置类,@Import部分修改如下:

@Import (value = { DemoApplicationListener1. class , DemoEventMulticaster. class })

  

  运行结果如下:

 



  • 方式二

  使用Spring管理的线程池支持事件多播器的异步;

 

  事件监听器



查看代码

 @Component
public class DemoApplicationListener2 implements ApplicationListener {
private final static Logger logger = LoggerFactory
.getLogger(DemoApplicationListener2. class );
@Override
@Async
public void onApplicationEvent(ApplicationEvent event) {
logger.info( "接收到一个事件:" + event);
}
}

   

  配置类



查看代码

 @EnableAsync
@Configuration
@Import (value = { DemoApplicationListener2. class })
public class ListenerConfig2 implements AsyncConfigurer {
private final static Logger logger = LoggerFactory.getLogger(ListenerConfig2. class );
// 获取线程池
@Override
public Executor getAsyncExecutor() {
// 定义线程池
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize( 4 );
// 设置线程池最大线程数
executor.setMaxPoolSize( 10 );
// 设置线程池队列容量
executor.setQueueCapacity( 1000 );
// 初始化
executor.initialize();
logger.info(String.valueOf(executor));
return executor;
}
}

  

  测试类:



查看代码

 @Test
public void listenerTest2() {
AnnotationConfigApplicationContext cOntext= new AnnotationConfigApplicationContext(
ListenerConfig2. class );
context.publishEvent( new ApplicationEvent( "发送测试消息" ) {
});
try {
TimeUnit.SECONDS.sleep( 5 );
}
catch (InterruptedException e) {
e.printStackTrace();
}
context.close();
}

  

  运行结果如下:

 

  也可以使用@EventListener事件注解;

  修改上面的事件监听器



查看代码

 @Component
public class DemoApplicationListener2 {
private final static Logger logger = LoggerFactory
.getLogger(DemoApplicationListener2. class );
@Async
@EventListener
public void onApplicationEvent(ApplicationEvent event) {
logger.info( "接收到一个事件:" + event);
}
}

 

  运行结果如下:

  参考:[https://docs.spring.io/spring-framework/docs/5.2.4.RELEASE/spring-framework-reference/core.html#context-functionality-events-annotation]

  



推荐阅读
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • 1、概述首先和大家一起回顾一下Java消息服务,在我之前的博客《Java消息队列-JMS概述》中,我为大家分析了:然后在另一篇博客《Java消息队列-ActiveMq实战》中 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • JavaWeb中读取文件资源的路径问题及解决方法
    在JavaWeb开发中,读取文件资源的路径是一个常见的问题。本文介绍了使用绝对路径和相对路径两种方法来解决这个问题,并给出了相应的代码示例。同时,还讨论了使用绝对路径的优缺点,以及如何正确使用相对路径来读取文件。通过本文的学习,读者可以掌握在JavaWeb中正确找到和读取文件资源的方法。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有