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一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
21天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
33 0
|
24天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
2天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
19 6
|
3天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
15 3
|
4天前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
16 0
|
6天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
14 1
|
7天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
99 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
10天前
|
Java 开发者 微服务
Spring Cloud原理详解
【5月更文挑战第4天】Spring Cloud是Spring生态系统中的微服务框架,包含配置管理、服务发现、断路器、API网关等工具,简化分布式系统开发。核心组件如Eureka(服务发现)、Config Server(配置中心)、Ribbon(负载均衡)、Hystrix(断路器)、Zuul(API网关)等。本文讨论了Spring Cloud的基本概念、核心组件、常见问题及解决策略,并提供代码示例,帮助开发者更好地理解和实践微服务架构。此外,还涵盖了服务通信方式、安全性、性能优化、自动化部署、服务网格和无服务器架构的融合等话题,揭示了微服务架构的未来趋势。
35 6
|
14天前
|
负载均衡 Java 开发者
Spring Cloud:一文读懂其原理与架构
Spring Cloud 是一套微服务解决方案,它整合了Netflix公司的多个开源框架,简化了分布式系统开发。Spring Cloud 提供了服务注册与发现、配置中心、消息总线、负载均衡、熔断机制等工具,让开发者可以快速地构建一些常见的微服务架构。
|
15天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。