Spring源码 --- 监听器的原理 (上)

简介: Spring源码 --- 监听器的原理

一. 监听器的使用



为什么要学习监听器呢?学习监听器主要学习监听器的设计思想。 比如,我们之前研究过的nacos,他就是使用监听器进行集成的。所以了解监听器的原理,就很重要了。

首先, 我们要知道监听器如何使用。


1.1 Spring事件的原理


原理: 是观察者模式


Spring的事件监听有三个组成部分:


1. 事件(ApplicationEvent):要广播,发送的消息. 监听器监听的事情

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

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

 

1.2 认识监听器


上面认识了监听器. 接下来看一个例子. 通过例子来理解.


就好比现在有一个消息, 比如说: 下单后减库存. 减库存就是一个事件, 这个事件需要一个事件播放器, 将事件播放出去. 然后另一端事件监听器, 接收到信息,进行处理.


比如:下面的demo


有一个订单Order :

package com.lxl.www.events;
/**
 * Description
 *
 * DATE 2020/11/17.
 *
 * @author lxl.
 */
public class Order {
    private Integer id;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}

接下来, 有一个订单事件. 订单的操作,带来的库存的增减. 就是一个订单事件

package com.lxl.www.events;
import org.springframework.context.ApplicationEvent;
import java.io.Serializable;
/**
 * Description
 * 订单的事件
 *
 * 事件的分类: 分为自定义事件和内置事件
 * DATE 2020/11/17.
 *
 * @author lxl.
 */
public class OrderEvent  extends ApplicationEvent implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public OrderEvent(Object event, String name) {
        super(event);
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

第三: 事件监听器 ,事件监听器用来监听事件. 当OrderEvent发布减库存消息的时候, 事件监听器就能听到.

package com.lxl.www.events;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
 * Description
 * OrderEvent的事件监听器
 *
 *
 * DATE 2020/11/17.
 *
 * @author lxl.
 */
@Component
public class OrderEventListenter implements ApplicationListener<OrderEvent> {
    /**
     * 当某一个事件发布的时候, 就会触发事件监听器
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(OrderEvent event) {
        if (event.getName().equals("减库存")) {
            System.out.println("事件监听器  监听到  减库存");
        }
    }
}

是不是和mq相差不多.

mq也是一个订阅者,一个发布者.

下面写一个main方法, 运行看看监听器的效果


package com.lxl.www.events;
import org.springframework.beans.factory.parsing.SourceExtractor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 *  监听器的使用
 */
public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
        /**
         * 使用场景: 比如有一个订单, 由用户下单了,那么对应的就要减库存.其实下单和减库存不需要是串行.
         * 通常, 我们会使用一个mq去处理减库存的情况. 也就是采用异步的方式.
         *
         * 那么, 监听器的远离和mq是类似的. 我们可以手动设置采用同步还是异步的方式处理.
         */
        Order order = new Order();
        order.setId(1);
        System.out.println("下单");
        // 发布事件. 当在这里发布事件, 那么就会被事件监听器监听到
        ctx.publishEvent(new OrderEvent(order, "减库存"));
        System.out.println("日志.....");
    }
}

输出结果


下单

事件监听器  监听到  减库存

日志.....

监听器使用的设计模式是: 观察者模式.

 

1.3 监听器的类型


监听器有两种类型: 一种是内置的监听器, 一种是自定义监听器.


1.3.1 内置监听器


spring设置了一个内置监听器的父类.


public abstract class ApplicationContextEvent extends ApplicationEvent {
    /**
     * Create a new ContextStartedEvent.
     * @param source the {@code ApplicationContext} that the event is raised for
     * (must not be {@code null})
     */
    public ApplicationContextEvent(ApplicationContext source) {
        super(source);
    }
    /**
     * Get the {@code ApplicationContext} that the event was raised for.
     */
    public final ApplicationContext getApplicationContext() {
        return (ApplicationContext) getSource();
    }
}


实现了ApplicationContextEvent的类就是内置的监听器. 我们使用快捷键ctrl + H, 查看都有哪些类实现了 ApplicationContextEvent

1187916-20201117062218575-1278101660.png


一共有5各类实现了ApplicationContextEvent.  

Event  说明
ContextRefreshEvent

当容器被实例化或者refresh时发布.如调用refresh()方法. 此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例bean都已被实例化,所有的容器对象

都已经准备好可使用. 如果容器支持热重载,则refresh()可以被触发多次(XmlWebApplicationContext支持热刷新, 而GenericApplicationContext不支持热刷新)

ContextStartedEvent 当容器启动时发布, 即调用start()方法, 已启用意味着所有的lifecycle都已显示收到了start的信号
ContextStoppedEvent 当容器停止时发布. 即调用stop()方法, 既所有的lifecycle bean都已显示接收了stop信号, 关闭的容器可以通过start()方法重启
ContextClosedEvent 当容器关闭时发布. 即调用close()方法, 关闭意味着所有的单例bean都已被销毁. 关闭的容器不能被重启或refresh()

 

1. ContextRefreshEvent: 当容器被实例化或者refresh时发布


我们来看看一下源码.

从refresh()源码进入.

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
        // 进入构造函数, 首先调用自身的构造方法this();
        // 调用自身的构造方法之前, 要先调用父类的构造方法
        this();
        // register配置注册类
        register(componentClasses);
        // ioc容器刷新接口--非常重要
        refresh();
    }
/**
     * refresh是spring最核心的方法, 里面包含了整个spring ioc的全过程, 包括spring加载bean到销毁bean的全过程
     * 学习spring, 就是学习里面的13个方法, 如果13个方法都学完了, 基本上就打通了
     * @throws BeansException
     * @throws IllegalStateException
     */
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 1. 准备刷新上下文环境
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            //2. 获取告诉子类初始化bean工厂, 不同工厂不同实现
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        ......
// Last step: publish corresponding event.
                //最后容器刷新 发布刷新时间(spring cloud是从这里启动的 )
                finishRefresh();
            }
      
      ......
    }
    }

进入到finishRefresh()方法

protected void finishRefresh() {
        // Clear context-level resource caches (such as ASM metadata from scanning).
        // 清除上下文缓存
        clearResourceCaches();
        // Initialize lifecycle processor for this context.
        // 注册lifecycleProcessor声明周期处理器
        // 作用: 当ApplicationContext启动或停止时, 他会通过LifecycleProcessor来与所有声明的bean进行交互
        initLifecycleProcessor();
        // Propagate refresh to lifecycle processor first.
        // 为实现了SmartLifeCycle并且isAutoStartup, 自动启动的Lifecycle调用start()方法
        getLifecycleProcessor().onRefresh();
    // 发布容器启动完毕事件
        publishEvent(new ContextRefreshedEvent(this));
        // Participate in LiveBeansView MBean, if active.
        LiveBeansView.registerApplicationContext(this);
    }

我们看到有一个发布事件. 这个事件的作用是通知容器已经启动完毕. 注意看, 里面发布的是什么事件? new ContextRefreshedEvent(this). 发布的是ContextRefreshedEvent事件.


下面有一个问题:  怎么样可以在所有的bean创建完以后做扩展代码呢?

上面我们说到了, 当所有的bean都创建完以后, 会调用publishEvent(new ContextRefreshedEvent(this));发布容器启动完毕的事件.


这时我们可以自定义一个监听器, 用来监听ContextRefreshedEvent事件.


/**
 * 自定义一个事件监听器, 用来监听ContextRefreshedEvent事件
 */
@Component
public class ContextRefreshedEventListener {
  /**
   * 声明这是一个事件监听器, 监听的是ContextRefreshedEvent事件.
   * @param event
   */
  @EventListener(ContextRefreshedEvent.class)
  public void onApplicationEvent(ContextRefreshedEvent event) {
      ....
    // 在所有的bean创建完以后, 写一些逻辑代码
  }
}

然后, 在里面写上我们需要在容器都创建完毕之后执行的逻辑代码.


2. ContextClosedEvent: 当容器关闭时发布

还是先来看源码, spring是在何时发布的这个事件.

protected void doClose() {
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing " + this);
            }
            LiveBeansView.unregisterApplicationContext(this);
            try {
                // Publish shutdown event.
                publishEvent(new ContextClosedEvent(this));
            }
            catch (Throwable ex) {
                logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
            }
            // Stop all Lifecycle beans, to avoid delays during individual destruction.
            if (this.lifecycleProcessor != null) {
                try {
                    this.lifecycleProcessor.onClose();
                }
                catch (Throwable ex) {
                    logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
                }
            }
            // Destroy all cached singletons in the context's BeanFactory.
            destroyBeans();
            // Close the state of this context itself.
            closeBeanFactory();
            // Let subclasses do some final clean-up if they wish...
            onClose();
            // Reset local application listeners to pre-refresh state.
            if (this.earlyApplicationListeners != null) {
                this.applicationListeners.clear();
                this.applicationListeners.addAll(this.earlyApplicationListeners);
            }
            // Switch to inactive.
            this.active.set(false);
        }
    }

在doClose()的时候, 发布了publishEvent(new ContextClosedEvent(this));事件

我们看一看具体发布的是什么事件呢? 就是ContextClosedEvent事件


假如: 我们想要在容器关闭的时候做一些扩展, 就可以写一个监听器, 在容器关闭的时候监听ContextClosedEvent事件

 

Spring内置的事件, 我们就不用再自己定义了. 我们需要做的就是定义一个监听器, 监听事件就可以了.

 

1.3.2 自定义监听器


不是spring定义的监听器, 也就是我们自己定义的监听器就是自定义监听器. 下面来看看自定义监听器的两种类型.


类型一: 基于接口

@Component
public class HelloEventListener implements ApplicationListener<OrderEvent> {
  @Override
  public void onApplicationEvent(OrderEvent event) {
      if (event.getName().equals("减库存")) {
        System.out.println("减库存....");
      }
  }
}

事件监听器需要实现ApplicationListener接口, 这是一个泛型接口, 泛型的类型就是事件的类型.


其次, 这个监听器需要是spring容器托管的bean, 因此加上了@Component注解, 里面只有一个方法onApplicationEvent, 就是事件触发时执行的内容.


类型二: 基于注解

@Component
public class OrderEventListener {
  @EventListener(OrderEvent.class)
  public void onApplicationEvent(OrderEvent event) {
    if (event.getName().equals("减库存")) {
      System.out.println("减库存....");
    }
  }
}

在方法上面添加注解@EventListener(OrderEvent.class) 监听的是哪个事件呢?OrderEvent.class


我们在定义监听器的时候, 可以选择是基于接口的方式还是基于注解的方式.


二. 监听器源码



首先, 监听器的声明,调用,都是在refresh()方法里面进行,我们先来看看refresh()的整体脉络. 其中标红的部分是和监听器有关系的模块.

1187916-20201123093827434-898991587.png

这里面的第五步, 第九步, 第十一步, 都详细的分析过. 下面主要看看和监听器有关的几步.


2.1 准备上下文环境prepareRefresh()


在准备上下文环境的时候, 我们看看做了哪些事情

1187916-20201123094154828-731841509.png

1. 设置了容器当期的状态, 是激活状态


2. 初始化了属性源initPropertySources();.


在AbstractApplicationContext类中没有实现这个方法. 这是一个父类定义的方法. 比如:我们可以自定义一个类, 然后重写initPropertySource, 在改方法中设置一个环境变量abc, 那么在容器启动的时候, 就会去环境变量中检查, 是否环境变量中有这个属性, 如果没有就会抛出异常.


3. 接下来就是验证上面环境变量中指定的属性是否存在了. getEnvironment().validateRequiredProperties(); 不存在就抛出异常MissingRequiredPropertiesException


4. 然后接下来,和事件有关的一步, 创建了早期的事件监听器

// 创建早期的事件监听器.
        // Store pre-refresh ApplicationListeners...
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        }
        else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }


这里有一个问题, 什么是早期的事件监听器呢? 早对应的就是晚了. 早期指的是多早呢?

早期事件指的是事件监听器还没有注册到事件多播器的时候.


早期定义的事件不需要手动的publishEvent, 在RegisterListener()阶段会自动发布早期事件.


什么是早期的事件监听器呢? 早对应的就是晚了. 早期指的是多早呢?


早期事件指的是事件监听器还没有注册到事件多播器的时候.


早期定义的事件不需要手动的publishEvent, 在RegisterListener()阶段会自动发布早期事件.

在这里就定义了一个集合, 这个集合就是后面事件监听器集合. 在这里只是进行的初始化

 

5. 初始化保存早期事件的集合


this.earlyApplicationEvents = new LinkedHashSet<>();

在第一步: 对事件的操作就是初始化. 一共初始化了两个集合, 一个是早期事件监听器集合, 一个是早期的事件集合

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
110 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
110 5
|
2月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
47 0
|
15天前
|
Java Spring
Java Spring Boot监听事件和处理事件
通过上述步骤,我们可以在Java Spring Boot应用中实现事件的发布和监听。事件驱动模型可以帮助我们实现组件间的松耦合,提升系统的可维护性和可扩展性。无论是处理业务逻辑还是系统事件,Spring Boot的事件机制都提供了强大的支持和灵活性。希望本文能为您的开发工作提供实用的指导和帮助。
67 15
|
13天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
106 14
|
1月前
|
缓存 安全 Java
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
54 2
Spring高手之路26——全方位掌握事务监听器
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
65 2
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
79 9