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版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
2月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
1月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
1月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
358 2
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
3月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
6月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
207 32
|
4月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
173 0
|
4月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)

热门文章

最新文章