SpringBoot源码(四):run() 方法解析(一)

run()方法:

public ConfigurableApplicationContext run(String... args) {
    // 记录应用启动时间
    long startTime = System.nanoTime();
    
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    // 创建 ConfigurableApplicationContext 对象
    ConfigurableApplicationContext context = null;
    // 设置系统的 awt (保证在没有检测到显示器的情况下(如linux服务器),SpringBoot应用也可以启动)
    configureHeadlessProperty();
    // 【】创建 SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 调用 SpringApplicationRunListener 的 starting 方法,发布了一个 ApplicationStartingEvent 事件 【】
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 【】准备运行时环境 Environment
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        // 
        configureIgnoreBeanInfo(environment);
        // 创建Banner对象、打印 Banner(会在控制台中打印 SPRING 图案,和 SpringBoot 的版本号)
        Banner printedBanner = printBanner(environment);
        // 【】创建IOC容器
        context = createApplicationContext();
        // 启动度量
        context.setApplicationStartup(this.applicationStartup);
        // 【】初始化IOC容器
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 【】刷新IOC容器(会同时在控制台打印Tomcat有关的信息)
        refreshContext(context);
        // 空实现
        afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            // 在控制台打印,消耗时间的信息:Started MyApplication in 209.698 seconds (JVM running for 215.496)
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        // 调用 SpringApplicationRunListener 的 started 方法,发布了一个 ApplicationStartedEvent 事件 【】
        listeners.started(context, timeTakenToStartup);
        // 回调所有的运行器
        callRunners(context, applicationArguments);
    }//..
    
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        // 调用 SpringApplicationRunListener 的 ready 方法,发布了一个 ApplicationReadyEvent 事件 【】
        listeners.ready(context, timeTakenToReady);
    }//..
    
    // 返回IOC容器(所以 SpringApplication.run() 方法是有返回值的)
    return context;
}


获取 SpringApplicationRunListeners

在 SpringApplication 的 run() 方法内,有这样一步:

SpringApplicationRunListeners = getRunListeners(args)

获取 SpringApplicationRunListeners ,SpringApplicationRunListeners 其实就是装SpringApplicationRunListener 的容器

SpringApplicationRunListener

SpringApplicationRunListener 看名字就可以知道它是一个监听器,只不过它只负责监听 run() 方法,由于 run() 方法过于复杂,且整个 run() 方法中涉及很多切入点和扩展点,留有一个监听器可以在预定义好的切入点中扩展自定义逻辑。

SpringApplicationRunListener 提供了一系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。下面我们对照源代码和注释来了解一下该接口都定义了哪些待实现的方法及功能:

public interface SpringApplicationRunListener {
	// 当 run() 调用时,会被立即调用,可用于非常早期的初始化工作
    default void starting(ConfigurableBootstrapContext bootstrapContext) {}
    // 当 environment 对象准备完成,在 IOC 容器创建之前,该方法被调用
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {}
	// 当 IOC 创建完成,资源还未被加载时,该方法被调用
    default void contextPrepared(ConfigurableApplicationContext context) {}
	// 当 IOC 资源加载完成,未被刷新时,该方法被调用
    default void contextLoaded(ConfigurableApplicationContext context) {}
	// 当 IOC 刷新完并启动之后,未调用 CommandLineRunner 和 ApplicationRunner 时,该方法被调用
    default void started(ConfigurableApplicationContext context, Duration timeTaken) {
 started(context); }
    // 所有准备工作就绪,调用该方法
    default void ready(ConfigurableApplicationContext context, Duration timeTaken) { running(context); }
	// 当应用程序出现错误时,调用该方法
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }   
    //...
}

可以看出,SpringApplicationRunListener 为 run 方法提供了各个运行阶段的监听事件处理功能:

listeners

继续分析 getRunListeners 方法:

SpringApplication # getRunListeners()

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };   
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}

SpringApplicationRunListeners 的构造方法传入了三个参数:这里关注第二个参数

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args) (这里把 this(当前SpringApplication 对象)传入了方法

会通过 SpringFactoriesLoader 加载并实例化外部定义的所有 SpringApplicationRunListener (默认只有一个:EventPublishingRunListener )
并且这些 SpringApplicationRunListener 必须有一个接收 (SpringApplication application,String[] args)参数类型的构造器

EventPublishingRunListener 是 SpringBoot 中针对 SpringApplicationRunListener 接口的唯一内建实现

注意看注释:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {  // 注意这里的 Object... args 参数包含了 SpringApplication 对象
    ClassLoader classLoader = getClassLoader();
    // 获取类路径下 META-INF/spring.factories 中所有的 SpringApplicationRunListener 的名字(默认只有一个:EventPublishingRunListener)
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化这个 EventPublishingRunListener(会利用 args 构建 EventPublishingRunListener对象)
    // 【注意】如果之后在 META-INF/spring.factories 中又扩展了许多 SpringApplicationRunListener,那么同样的,会利用 args 构建这些 SpringApplicationRunListener 对象
    // 即,这些实例化好的这些 SpringApplicationRunListener 一定会持有 SpringApplication 对象,从而拥有它里面的一些属性(如,可以获取所有的监听器对象)
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    // 返回
    return instances;
}

EventPublishingRunListener 的构造方法如下:

【注】创建好的 EventPublishingRunListener 对象会拥有 SpringApplication 对象里的 ApplicationListener 等属性

public EventPublishingRunListener(SpringApplication application, String[] args) {
    // 持有了 SpringApplication 对象
    this.application = application;
    this.args = args;
    // 初始化了一个事件多播器 SimpleApplicationEventMulticaster
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    // 将 SpringApplication 对象里的 ApplicationListener 全部加到了多播器中
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener); 
    }
}

通过源代码可以看出,该类的构造方法符合 SpringApplicationRunListener 所需的构造方法参数要求,该方法依次传递了 SpringApplication 和 String[] 类型。

在构造方法中初始化了该类的3个成员变量:

​ ① application:类型为 SpringApplication,是当前运行的 SpringApplication 实例

​ ② args:启动程序时的命令参数

​ ③ initialMulticaster:类型为 SimpleApplicationEventMulticaster,事件广播器

SpringBoot 完成基本的初始化之后,会遍历 SpringApplication 的所有 ApplicationListener 实例,并将它们与 SimpleApplicationEventMulticaster 进行关联,方便后续将事件传递给所有的监听器。

EventPublishingRunListener 针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同:

​ ● 程序启动到某个步骤后,调用 EventPublishingRunListener 的某个方法

​ ● EventPublishingRunListener 的具体方法将 application 参数和 args 参数封装到对应的事件中(这里的事件均为 SpringApplicationEvent 的实现类)

​ ● 通过成员变量 initialMulticaster 的multicastEvent方法对事件进行广播,或通过该方法的 ConfigurableApplicationContext context 参数的 publishEvent() 方法来对事件进行发布

​ ● 对应的 ApplicationListener 被触发,执行相应的业务逻辑

如:EventPublishingRunListener 的 starting() 方法:

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}


listeners.starting()

之后的一步: 调用 SpringApplicationRunListeners 的 starting 方法:【注】此时 SpringApplicationRunListeners 已经拥有了之前创建好的 EventPublishingRunListener 对象。

SpringApplicationRunListeners # starting()

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),  
                    (step) -> {
                        if (mainApplicationClass != null) {
                            step.tag("mainApplicationClass", mainApplicationClass.getName());
                        }
                    });
}

SpringApplicationRunListeners # doWithListeners()

private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) {
    StartupStep step = this.applicationStartup.start(stepName);
    // 遍历 this.listeners(目前只有一个EventPublishingRunListener),执行它们的 starting(bootstrapContext) 方法
    this.listeners.forEach(listenerAction); 
    if (stepAction != null) {
        stepAction.accept(step);  
    }
   
    step.end();
}

EventPublishingRunListener # starting()

可以看到方法内部,利用事件多播器,发布了一个 ApplicationStartingEvent 事件

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

以下三个监听器会监听到,并调用它们的 onApplicationEvent 方法:

① LoggingApplicationListener、
② BackgroundPreinitializer、
③ DelegatingApplicationListener

它们都注册在 META-INF/spring.factories 中

总结

【注】SpringApplicationRunListeners,它相当于一系列 SpringApplicationRunListener 的组合 这些 SpringApplicationRunListener 的实现类都在 META-INF/spring.factories 中注册

(目前只有一个:EventPublishingRunListener)

private final List<SpringApplicationRunListener> listeners

如:调用 SpringApplicationRunListeners 的 starting(),则会遍历 List< SpringApplicationRunListener> listeners 里所有的 SpringApplicationRunListener ,依次调用它们的 starting()

其实就只有一个: EventPublishingRunListener,调用它的 starting(),而方法内部就会通过广播器将对应的事件广播给EventPublishingRunListener 所持有的 ApplicationListener 对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908003.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FCP-报表开发工程师 考试内容

报表开发工程师考试内容 文章目录 报表开发工程师考试内容1. 报考说明2. 考试内容2.1 FineReport 产品能力 80%2.2 前端语言 10%2.3 SQL 编程语言 5%2.4 运维能力 5 % 3. 考试知识结构3.1 复杂报表开发 20% ~ 30%3.2 数据填报功能 10%3.3 决策报表 0%~10%3.4 层次坐标专题 0%~5…

Spring中的资源以及分类

Spring中的资源都被封装成 Resource 对象 以上是我测试代码的项目编译后的目录结构&#xff0c;target 所在的目录是 D:\\IdeaProjects\\study-spring\\ public void printStream(InputStream inputStream) throws IOException {Reader reader new InputStreamReader(input…

xtu oj 连接字符串

文章目录 回顾思路代码 回顾 AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串xtu oj 神经网络xtu oj 1167 逆序数&#xff08;大数据&#xff09;xtu oj 原根xtu oj 不定方程的正整数解xtu oj 最多的可变换字符串…

[Prometheus学习笔记]从架构到案例,一站式教程

文章目录 Prometheus 优势Prometheus 的组件、架构Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标&#xff0c;它在本地存储所有抓取到的样本数据&#xff0c;并对此数据执行一系列规则&#xff0c;以汇总和记录现有数据的新时间序列或生成告警。可以通…

鸿蒙打包hvigorw clean报错No npmrc file is matched in the current user folder解决

问题 在执行hvigorw clean等命令时&#xff0c;报错如下&#xff1a; Error: The hvigor depends on the npmrc file. No npmrc file is matched in the current user folder. Configure the npmrc file first解决方案 在用户当前目录下新建.npmrc文件&#xff0c;并配置如下…

读数据工程之道:设计和构建健壮的数据系统27转换

1. 转换 1.1. 转换与查询不同 1.1.1. 查询是根据过滤和连接逻辑从各种来源检索数据 1.1.2. 转换将结果持久化&#xff0c;供其他转换或查询使用 1.1.2.1. 结果可以被短暂地或永久地保存 1.1.3. 除了持久性&#xff0c;转换区别于查询的另一个特点是复杂性 1.1.3.1. 你可能会建…

市场分化!汽车零部件「变天」

全球汽车市场的动荡不安&#xff0c;还在持续。 本周&#xff0c;全球TOP20汽车零部件公司—安波福&#xff08;Aptiv&#xff09;发布2024年第三季度财报显示&#xff0c;三季度公司经调整后确认收入同比下降6%&#xff1b;按照区域市场来看&#xff0c;也几乎是清一色的下滑景…

Java面试经典 150 题.P26. 删除有序数组中的重复项(003)

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public int removeDuplicates(int[] nums) …

ONLYOFFICE 8.2版本桌面编辑器评测

目录 ONLYOFFICE 8.2版本桌面编辑器评测一、引言二、ONLYOFFICE 桌面编辑器概述2.1 功能特点2.2 系统支持2.3 数据表&#xff1a;ONLYOFFICE 桌面编辑器功能概述 三、ONLYOFFICE 协作空间3.1 协作功能3.2 部署和集成3.3 数据表&#xff1a;ONLYOFFICE 协作空间功能概述 四、ONL…

管道缺陷图像分割系统:入门训练营

管道缺陷图像分割系统源码&#xff06;数据集分享 [yolov8-seg-vanillanet&#xff06;yolov8-seg-C2f-DiverseBranchBlock等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源…

【文件处理】二、批处理(.bat) - 批量修改文件拓展名

一、简介 批处理&#xff08;Batch Processing&#xff09;是一种广泛应用于Dos和Windows系统种的脚本语言&#xff1b;它允许用户将一些列名称或程序组合在一起&#xff0c;形成可一次性执行的批处理文件。批处理文件的拓展名通常为".bat"、“.cmd”、".btm&q…

go-logger v0.27.0 - 并发性能为官方库 10 倍

go-logger是一个高性能的 golang 日志库&#xff0c;旨在提供快速、轻量级的日志记录功能 Github 使用文档 v0.27.0 更新内容 优化内存分配优化写数据性能增加日志属性自定义函数增加各个日志级别格式化打印函数 说明 性能优化是该版本最重要的更新内容。性能优化的结果&…

大零售时代下融合发展的新路径:定制开发技术的应用与思考

摘要&#xff1a;本文探讨在大零售背景下&#xff0c;传统零售边界模糊&#xff0c;融合成为趋势。分析大零售包含的跨行业跨业态融合等三个层面&#xff0c;重点阐述定制开发技术中的 21 链动模式、AI 智能名片和 S2B2C 商城小程序在推动大零售发展中的作用和意义&#xff0c;…

qt QSplitter详解

1、概述 QSplitter是Qt框架中的一个布局管理器类&#xff0c;它允许用户在应用程序窗口中创建可拖动的分隔器&#xff0c;以便动态地调整多个子窗口或控件的大小。QSplitter非常适合用于分割、重新排列和管理用户界面中的多个区域&#xff0c;提供了一种直观且灵活的方式来控制…

【6G 需求与定义】ITU(国际电联)对全球6G标准的愿景

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G技术研究。 博客内容主要围绕…

《高频电子线路》—— 调幅电路

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 目录 调幅电路 分类 小结 模拟相乘器调幅电路 流程图比较 集成电路MC1596G实例 小结 单边带调幅电路 滤波法 移相法 修正的移相…

[java][框架]springMVC(1/2)

目标 知道SpringMVC的优点编写SpringMVC入门案例使用PostMan发送请求掌握普通类型参数传递掌握POJO类型参数传递掌握json数据参数传递掌握响应json数据掌握rest风格快速开发 一、SpringMVC简介 1 SpringMVC概述 问题导入 SpringMVC框架有什么优点&#xff1f; 1.1 Spring…

零基础玩转IPC之——如何实现远程实时查看监控视频(P2P)

P2P是peer-to-peer的简称&#xff0c;又称为点对点技术&#xff0c;是没有中心服务器、依靠用户群节点进行信息交换的对等式网络。区别于传统的C/S中央服务器结构&#xff0c;P2P网络中每一个用户节点即是客户端又是服务端&#xff0c;能同时作为服务器给其他节点提供服务。 优…

医院场景下电气设备的谐波治理

随着各种电气设备在医院诊疗中的使用越来越广泛&#xff0c;谐波源越来越多&#xff0c;造成线路及电源处的谐波污染也越来越大。某医院变电所有电源进线柜3个&#xff0c;现场的配电系统存在以下问题&#xff1a;出现配电线路损耗增大、发热、缩短绝缘寿命&#xff1b;出现电容…

电赛入门之软件stm32keil+cubemx

hal库可以帮我们一键生成许多基本配置&#xff0c;就不需要自己写了&#xff0c;用多了hal库就会发现原来用基本库的时候都过的什么苦日子&#xff08;笑 下面我们以f103c8t6&#xff0c;也就是经典的最小核心板来演示 一、配置工程 首先来新建一个工程 这里我们配置rcc和sys&…