spring boot 源码赏析之事件监听

使用spring Boot已经快1年多了,期间一直想点开springboot源码查看,但由于种种原因一直未能如愿(主要是人类的惰性。。。),今天就拿springboot 的监听事件祭刀。

springboot 中常用的事件监听主要有ApplicationStartedEvent,ApplicationEnviromentPreparedEvent,ApplicationPreparedEvent,ApplicationStoppedEvent等。用于监听springboot生命周期中的各种事件。

说到监听都会不由自主想到观察者模式,springboot的事件监听实现也没出意外,它的实现机制也是一个典型的观察者模式,只不过稍微复杂点罢了。springboot将观察者放入一个list中委托

SimpleApplicationEventMulticaster管理。这里有个有意思的地方,srpingboot在实例化SpringApplicationRunListener 时并没有通过正常的途径(比如new,或者其他实例化方法),而是将接口的实现类交由

SpringFactoriesLoader去 META-INF/spring.factories 下装载:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

这里通过classLoader的getResources方法将所有的META-INF/spring.factories资源都装载进来。

springboot为什么要不惜花费这么大代价去装载一个接口的实现呢,稍微分析一下还是很清晰的,首先,将接口和实现分离,这是典型的依赖注入,如果从单个应用程序来看,大不必这样。关键就在SpringFactoriesLoader里,如上所说,SpringFactoriesLoader是将所有的配置都装载进来,也就是说springboot在为以后的扩展留了条后路,当有新的jar包(意味着又升级啦)加进来,并且需要添加新的的监听事件类型时,就不用修改原有的代码。这就是传说中的对修改关闭对扩展开放的典型例子。

接着说,那么SpringFactoriesLoader装载的实例到底是什么呢, EventPublishingRunListener!

这货也就是个中间类,承接了事件的管理和事件的广播,正真的主角是SimpleApplicationEventMulticaster类,该类的实例在EventPublishingRunListener实例化时被创建出来。

里面有个ListenerRetriever内部类,这个类有两个作用,一个default,SimpleApplicationEventMulticaster初始化时被创建,含有所有的观察者,并作为各种操作的同步锁,还有就是将观察者分类,分类的依据有两个,

一个是事件类型eventType,还有一个是事件原始对象类型sourceType,sourceType是每个事件都会有的,这里是applicationContext。,分类好后,会在事件发生时由最上层SpringApplication发起通知,最后由SimpleApplicationEventMulticaster对象分别调用每个具体的观察者:

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                listener.onApplicationEvent(event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            try {
                listener.onApplicationEvent(event);
            }
            catch (ClassCastException ex) {
                String msg = ex.getMessage();
                if (msg == null || msg.startsWith(event.getClass().getName())) {
                    // Possibly a lambda-defined listener which we could not resolve the generic event type for
                    Log logger = LogFactory.getLog(getClass());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Non-matching event type for listener: " + listener, ex);
                    }
                }
                else {
                    throw ex;
                }
            }
        }
    }

至此,一个完整的从事件注册到事件出发再到事件执行完毕的流程结束。山路十八弯,沿途风景多,细细品味还是会有一番收获的。这当中仍有许多细节未能一一道出,比如hashmap的hashCode的重写分析,比如同步锁的使用原因等等。

转载请注明出处。

时间: 2024-10-13 12:23:53

spring boot 源码赏析之事件监听的相关文章

Spring Boot源码中模块详解

Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1.x 二.模块 Spring Boot 包含许多模块,以下是一些简单的概述: 1,spring-boot 为Spring Boot其他部分功能提供主要的lib包,其中包含:(1)SpringApplication类提供了静态便利的方法使编写独立的SpringApplication更加容易.它唯一的任

spring boot 源码解析 启动流程

spring boot 源码解析 启动流程 在面试过程中经常被问到过spring boot的启动流程,今天就翻一下源码整体看一下: 首先,新建一个启动类,可以看到是首先调用的SpringApplication的静态方法run @SpringBootApplication public class SourceReadApplillcation { public static void main(String[] args) { SpringApplication.run(SourceReadAp

曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 大体思路 总体来说,bean definition是什么,我们前面几讲,说了个大概了:目前,我们将聚焦于怎么获取bean definition. 我们这次做个实验,就是将bean definition(一共两个bean,有依赖关系,依赖是手动指定的)定义在json文件内,然后自定义一个applicat

曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de

曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean de