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

news2024/11/26 4:42:23

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 对象

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP .NET CORE 6 在项目中集成WatchDog开源项目

概念 WatchDog是一个开源的项目&#xff0c;可以实现对.Net 应用程序和API实现实时应用日志和性能监控平台。可以实现实时记录和查看应用程序中的消息、事件、HTTP请求和响应&#xff0c;以及运行时捕获的异常&#xff0c;有效帮助开发人员去排查应用异常&#xff0c;提升开发效…

分类算法——决策树 详解

决策树的底层原理 决策树是一种常用的分类和回归算法&#xff0c;其基本原理是通过一系列的简单决策&#xff0c;将数据集划分为多个子集&#xff0c;从而实现分类。决策树的核心思想是通过树形结构表示决策过程&#xff0c;节点代表特征&#xff0c;边代表决策&#xff0c;叶子…

python 使用进程池并发执行 SQL 语句

这段代码使用了 Python 的 multiprocessing 模块来实现真正的并行处理&#xff0c;绕过 Python 的全局解释器锁&#xff08;GIL&#xff09;限制&#xff0c;从而在多核 CPU 上并发执行多个 SQL 语句。 from pyhive import hive import multiprocessing# 建立连接 conn hive.…

[ 问题解决篇 ] win11中本地组策略编辑器gpedit.msc打不开(gpedit.msc缺失)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

[Python学习日记-55] 软件开发目录设计规范

[Python学习日记-55] 软件开发目录设计规范 简介 为什么要设计好目录结构&#xff1f; 目录组织方式 关于 README 的内容 关于 setup.py 和 requirements.txt 关于配置文件的使用方法 简介 我们在浏览一些开源项目或者是一些安装后的软件的时候会发现&#xff0c;不同的两…

18.农产品销售系统(基于springboot和vue的Java项目)

目录 1.系统的受众说明 2.开发环境与技术 2.1 Java语言 2.2 MYSQL数据库 2.3 IDEA开发工具 2.4 Spring Boot框架 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信…

嵌入式常用功能之通讯协议1--IIC

嵌入式常用功能之通讯协议1--串口 嵌入式常用功能之通讯协议1--IIC&#xff08;本文&#xff09; 嵌入式常用功能之通讯协议1--SPI 一、IIC总线协议介绍 Inter-Integrated Circuit(集成电路总线&#xff09;&#xff0c;是由 Philips 半导体公司&#xff08;现在的 NXP 半导体…

一位纯理科生,跨界自学中医,自行组方治好胃病、颈椎病与高血脂症,并在最权威的中国中医药出版社出版壹本专业中医图书!

这是一位铁杆中医迷&#xff0c; 也是《神农本草经——精注易读本》的作者。 希望更多的人能够受到启发&#xff0c;感受中医之神奇&#xff0c;敢于跨界&#xff0c;爱好中医&#xff0c;学习中医&#xff01; 一个病人以自己的切身感受与诊断&#xff0c;并使之汤药治愈疾病&…

java项目之个人博客系统的设计与实现(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; springboot个人博客系统的…

使用 Sortable.js 库 实现 Vue3 elementPlus 的 el-table 拖拽排序

文章目录 实现效果Sortable.js介绍下载依赖添加类名导入sortablejs初始化拖拽实例拖拽完成后的处理总结 在开发过程中&#xff0c;我们经常需要处理表格数据&#xff0c;并为用户提供便捷的排序方式。特别是在需要管理长列表、分类数据或动态内容时&#xff0c;拖拽排序功能显得…

使用Kafka构建大规模消息传递系统

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Kafka构建大规模消息传递系统 引言 Kafka 简介 安装 Kafka 创建主题 生产者 消费者 高级特性 分区 持久化 消费者组 消息确认…

队列(Queue)的介绍与实现

文章目录 队列队列的概念及结构 队列的实现初始化队列销毁队列队尾入队列队头出队列获取队列头部元素检测队列是否为空获取队列中有效元素个数 队列 队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。队列遵…

【大模型之Graph RAG系列之二】对比传统RAG技术中使用的向量搜索技术,知识图谱有哪些优缺点?

向量搜索和知识图谱是两项用于改善搜索体验的重要技术。结合这两种技术形成的Graph RAG可以进一步提高搜索的准确性和上下文相关性。本文将深入对比向量搜索和知识图谱&#xff0c;让读者快速了解这两种技术的原理及优缺点&#xff0c;以便于将来的技术决策。 向量搜索 向量搜…

电赛入门之软件stm32keil+cubemx

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

从“技术深耕”到“品牌绽放”,解码遨游通讯的高成长路径!

在粤港澳大湾区这片充满活力的土地上&#xff0c;科技创新正以前所未有的速度推动着各行各业的发展。在这样一个充满机遇与挑战的环境中&#xff0c;遨游通讯以其在危险作业场景和应急救援场景中提供的定制化智能终端解决方案&#xff0c;脱颖而出&#xff0c;成为危急特赛道的…

golang通用后台管理系统02(RSA加密解密,登录密码加密解密)

参考&#xff1a;https://blog.csdn.net/lady_killer9/article/details/118026802 1.加密解密工具类PasswordUtil.go package utilimport ("crypto/rand""crypto/rsa""crypto/x509""encoding/pem""fmt""log"&qu…

【HarmonyOS NEXT】在 HarmonyOS NEXT 中实现优雅的加载动画

【HarmonyOS NEXT】在 HarmonyOS NEXT 中实现优雅的加载动画 在移动应用开发中&#xff0c;加载动画是提升用户体验的重要工具。在应用程序处理数据或加载页面时&#xff0c;为用户提供视觉反馈尤为关键。在这篇博客中&#xff0c;我们将探讨如何在 HarmonyOS NEXT 中使用 Sta…

群控系统服务端开发模式-应用开发-菜单功能开发

为什么优先开发菜单&#xff0c;而不是优先开发管理员&#xff1f;查看一下程序草图就明白&#xff0c;还有一个重点就是&#xff0c;管理员需要添加图片&#xff0c;而我还没有封装上传工具及上传目标。 一、添加路由 在根目录下route文件夹下的app.php文件里面&#xff0c;添…

服务器新建用户

文章目录 前言一、步骤二、问题三、赋予管理员权限总结 前言 环境&#xff1a; 一、步骤 创建用户需要管理员权限sudo sudo useradd tang为用户设置密码 sudo passwd tang设置密码后&#xff0c;可以尝试使用 su 切换到 tang 用户&#xff0c;确保该用户可以正常使用&#…

AI产品独立开发变现实战营

亮点&#xff1a; 1、三大真实商业项目&#xff0c;商业盈利、AI产品开发综合能力提升 2、掌握一人公司、副业产品设计-开发-运营-盈利落地全流程 3、大牛私藏AI盈利工具倾囊相授 4、借势增加睡后收入&#xff0c;从容应对裁员大环境与年龄危机 大纲&#xff1a; 第1章 独立…