Spring笔记——通过源码理解Spring 中事件发布

女爷i 2022-06-17 03:50 198阅读 0赞

Spring 中ApplicationContext 容器在BeanFactory 基础上增加了许多功能,现在单独记录下其中事件发布功能是如何实现的。

一、Spring 中与事件概览

关于Java 中事件机制这篇文章中自己已经整理: 。

弄清楚Java 中的事件原理,无非要弄清楚三个角色:事件源(source)、事件对象(EventObject)、事件监听器(EventListener)。

注:Java 中的java.util 包中的EventObject 与EventListener 是所有事件对象与监听器的最基础的类。

Spring 中是如何规划这三个角色的呢?

角色一:事件对象:

基础类为org.springframework.context.ApplicationEvent

源码:

  1. public abstract class ApplicationEvent extends EventObject {
  2. private static final long serialVersionUID = 7099057708183571937L;
  3. private final long timestamp;
  4. public ApplicationEvent(Object source) {
  5. super(source);
  6. this.timestamp = System.currentTimeMillis();
  7. }
  8. public final long getTimestamp() {
  9. return this.timestamp;
  10. }
  11. }

事件对象继承关系:

说明:其中ApplicationContextEvent 事件传入ApplicationContext 作为事件源,适合于所有Java 应用;RequestHandleEvent 中传入Object 对象作为事件源,且构造函数中附带了SessionID、请求地址、主机地址等信息,可见是专用于 Web 工程的。 角色二:事件源: 从上面可知,对于不同应用事件源是不同的。此处主要学习Spring ,所以只讨论子类ApplicationContextEvent 事件对象的事件源:ApplicationContext 。 ApplicationContext 与事件有关的继承关系: 说明:ApplicationContext 直接实现了ApplicationEventPublisher 接口,该接口中定义了publishEvent 方法,图上所示的类是全部实现了该接口方法的,而另外有些直接或间接实现了接口ApplicationContext 的类并没有实现publishEvent 方法,只是被动的继承了该方法。publishEvent 的具体实现位置在图中标记的AbstractApplicationContext 抽象类中。 监听器是在事件源中被调用的,那么事件源ApplicationContext 是怎么存放这些监听器实例的?在ApplicationContext 直接子类中,Spring 定义了所有监听器实例为一个集合:

  1. private Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();

Java 中事件监听器是在事件源中被调用执行,此处即ApplicationContext 的子类对象中。ApplicationContext 的publishEvent 方法的功能简单的理解就是,通过执行所有监听器实例的事件方法,判断该事件满足哪个监听器的执行条件,然后相关代码块。具体的过程便是在publishEvent 方法中完成的。此方法中关键在于标记1 处,后面仔细分析。

publishEvent 实现的源码:

  1. public void publishEvent(ApplicationEvent event) {
  2. Assert.notNull(event, "Event must not be null");
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Publishing event in " + getDisplayName() + ": " + event);
  5. }
  6. getApplicationEventMulticaster().multicastEvent(event);//即执行applicationListeners 集合中所有监听器的事件方法。标记1
  7. if (this.parent != null) {
  8. this.parent.publishEvent(event);
  9. }
  10. }

角色三:监听器类:

基础类为:org.springframework.context.ApplicationListener

继承关系:

Center

二、Spring 事件发布实现

上面已经知道,ApplicationContext 对象作为事件源,ApplicationEvent 作为事件对象基础类,ApplicationListener 作为事件监听器基础类。

所以,可以做一个简单的事件发布步骤为:

1、定义一个事件对象类:

  1. class MyApplicationEvent extends ApplicationEvent {
  2. public MyApplicationEvent(Object source){super(source);}
  3. }

2、定义一个处理上面MyApplicationEvent 事件的监听器类:

  1. class MyApplicationListener implements ApplicationListener{
  2. public void onApplicationEvent(ApplicationEvent e){ //ApplicationEvent的事件处理句柄为onApplicationEvent
  3. if(e instanceof MyApplicationEvent){
  4. System.out.println("收到了MyApplicationEvent 事件,事件源为:"+e.getSource());
  5. }
  6. }
  7. }

3、定义一个事件发布类,将ApplicationContext 作为事件源:

  1. class Publisher implements ApplicationContextAware{
  2. private ApplicationContext appCtx=null;
  3. public void setApplicationContext(ApplicationContext appCtx) throws BeansException{
  4. this.appCtx=appCtx;
  5. }
  6. public void publishMyApplicationEvent(){
  7. /*以String 对象作为事件源*/
  8. String strSource=new String("我是事件源");
  9. /*这就是要实现ApplicationContextAware 接口的原因,因为需要当前应用的ApplicationContext 对象*/
  10. MyApplicationEvent event=new MyApplication(strSource);
  11. /*使用ApplicationContext 对象的publishEvent 方法发布事件*/
  12. this.appCtx.publishEvent(event);
  13. }
  14. }

说明:ApplicationConextAware 接口的方法是ApplicationContext实例自动检测调用的,具体怎么调用没有细究,还有其他几个***Aware 接口也是如此。

4、配置myconfig.xml:省略头尾后

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <bean id="listener" class="com.milan.MyApplicationListener" />
  6. <bean id="publisher" class="com.milan.Publisher"/>
  7. </beans>

5、测试类。加载xml文件获得ApplicationContext 对象,通过getBean获得publisher 对象,执行其publishMyApplicationEvent 即可。

  1. public class Main{
  2. public static void main(String[] args){
  3. ApplicationContext appContext=new ClassPathXmlApplicationContext("com/milan/myconfig.xml");
  4. Publisher publisher=(Publisher) appContext.getBean("publisher");
  5. publisher.publishMyApplicationEvent();
  6. }
  7. }

注:演示的事件对象只是实现了最基础的ApplicationEvent 类,所以可以是任何Object对象作为事件源,但如果是实现ApplicationContextEvent 接口,此时必须以ApplicationContext 作为事件源,又该如何发布事件呢?应用场景又如何?后面讨论。

6、结果:

Center 1

三、publishEvent 调用监听器细节

上面演示的只是简单的调用了publishEvent,而没有关注publishEvent 实现细节。回到前面:

  1. public void publishEvent(ApplicationEvent event) {
  2. Assert.notNull(event, "Event must not be null");
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Publishing event in " + getDisplayName() + ": " + event);
  5. }
  6. getApplicationEventMulticaster().multicastEvent(event);//即执行applicationListeners 集合中所有监听器的事件方法。标记1
  7. if (this.parent != null) {
  8. this.parent.publishEvent(event);
  9. }
  10. }

通过查看 标记1 处的函数细节,可发现ApplicationContext 容器管理所有监听器类是通过 ApplicationEventMulticaster 类实现的。该接口定义如下:

  1. public interface ApplicationEventMulticaster {
  2. void addApplicationListener(ApplicationListener listener);
  3. void addApplicationListenerBean(String listenerBeanName);
  4. void removeApplicationListener(ApplicationListener listener);
  5. void removeApplicationListenerBean(String listenerBeanName);
  6. void removeAllListeners();
  7. void multicastEvent(ApplicationEvent event);
  8. }

标记1处的 getApplicationEventMulticaster().multicastEvent(event) 就是调用ApplicationEventMulticaseter 分发类的multicastEvent 实现执行ApplicationContext 实例中的成员变量applicationListeners 集合中的所有监听器的onApplicationEvent 方法,达到事件通知的目的。具体过程如下:

1、创建ApplicationContext 实例时便初始化监听器管理接口:

  1. protected void initApplicationEventMulticaster() {
  2. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  3. if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { //如果已经创建过,直接获取
  4. this.applicationEventMulticaster =
  5. beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
  6. if (logger.isDebugEnabled()) {
  7. logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
  8. }
  9. }
  10. else {
  11. this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);//第一次创建
  12. beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
  13. if (logger.isDebugEnabled()) {
  14. logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
  15. APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
  16. "': using default [" + this.applicationEventMulticaster + "]");
  17. }
  18. }
  19. }

2、将监听器用bean标签声明在xml中,即完成了监听器的添加。

3、publishEvent 时,最终在监听器管理接口的实现类中的 multicastEvent 方法中实现事件通知:

  1. public void multicastEvent(final ApplicationEvent event) {
  2. for (final ApplicationListener listener : getApplicationListeners(event)) { //遍历监听器
  3. Executor executor = getTaskExecutor();
  4. if (executor != null) { //如果有帮手,找帮手完成
  5. executor.execute(new Runnable() {
  6. @SuppressWarnings("unchecked")
  7. public void run() {
  8. listener.onApplicationEvent(event);
  9. }
  10. });
  11. }
  12. else { //只能老老实实自己完成
  13. listener.onApplicationEvent(event);
  14. }
  15. }
  16. }

知识点:使用Executor 类开启子线程处理耗时任务。

四、执行过程中的异常及解决办法

抛出异常:
Exception in thread “main” java.lang.NoClassDefFoundError: org/springframework/aop/support/AopUtils

原因分析:

网友解答:由于你涉及到面向切面编程,所以你需要AOP相关的jar包,不仅需要aop包,还需要aspectj相关的包,因为你用到ApplicationListener这种监听类。
百度知道

发表评论

表情:
评论列表 (有 0 条评论,198人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring理解

    Spring是一个开源的Java框架,广泛用于构建企业级应用程序。它提供了一系列的功能和特性,包括依赖注入、面向切面编程、控制反转等,以简化开发过程并提高代码的可维护性和可测试

    相关 Spring 事件发布

    前言 事件发布是 Spring 框架中最容易被忽视的功能之一,但实际上它是一个很有用的功能。使用事件机制可以将同一个应用系统内互相耦合的代码进行解耦,并且可以将事件与 S