spring boot启动流程解析

引言


欢迎阅读本篇文章,本文将深入探讨Spring Boot启动流程的内部机制。作为一个快速开发和简化配置的框架,Spring Boot已经成为许多开发者的首选工具。但是,了解Spring Boot的启动流程,对于更好地理解其运行原理以及解决潜在问题非常重要。通过本文,您将逐步了解Spring Boot应用程序背后的启动流程,并探索其中涉及的核心组件和关键步骤。

概述


在本文中,我们将从头到尾详细解析Spring Boot的启动流程。首先,我们会回顾一些Spring Boot的基础知识,包括自动配置和依赖管理等核心概念。接着,我们会对Spring Boot的启动类进行分析,了解其初始化过程。然后,我们将探讨SpringApplication类的作用和功能,介绍其构造函数的参数、属性配置文件的加载以及环境配置等。随后,我们将研究Spring Boot的自动配置机制,包括条件注解、自动配置类的加载和执行过程。此外,我们还会涉及到Spring Boot启动过程中的监听器和事件,以及Bean的加载和初始化过程。

通过本文,您将更深入地了解Spring Boot应用程序的启动流程,并能够更好地理解其运行原理和使用技巧。无论您是初学者还是有经验的开发者,本文都将为您提供全面的指导,帮助您在使用Spring Boot时更加得心应手。让我们开始探索Spring Boot启动流程的奥秘吧!

自动配置


在开始之前,我们先了解一下关于spring boot的自动配置。Spring Boot的自动配置就是根据你添加的依赖包,它会自动帮你进行基础配置。比如你添加了MySQL驱动的依赖,那Spring Boot就知道你要操作数据库,然后它就会自动配置好一个数据源等相关内容。所以你只需要提供一下数据库连接信息,就能直接用了,不用像传统Spring那样手动配置一大堆东西。这种特性大大简化了开发流程,让我们更专注于业务代码的编写。

以下是官方文档解释:
pPM7diQ.png

根据官方文档附录C Auto-configuration Classes可以看到spring boot提供的自动配置来源于 ‘spring-boot-autoconfigure’ 和 ‘spring-boot-actuator-autoconfigure’ 模块

  • The following auto-configuration classes are from the spring-boot-autoconfigure module
  • The following auto-configuration classes are from the spring-boot-actuator-autoconfigure module

依赖管理


我们在pom.xml中引入了springboot父项目之后,再去引入某些依赖时,发现并不需要指定版本号。

pPMbyUU.png

我们按住Ctrl,点进去spring-boot-starter-parent父项目pom文件内,发现他还有一层夫类

pPMboVK.png

这时候我们再点进去他的父类 ‘spring-boot-dependencies’ ,进入他的pom文件。

然后就能发现,在 '‘spring-boot-dependencies’ 类中,已经指定了多个依赖的版本号。

pPMbbPe.png

根据**“如果我们在引入依赖时没有指定版本号,那么就默认使用父项目中的版本号”**,我们可以得出一个结论:

在引入springboot父项目后,不需要再指定某些依赖的版本号是因为在spring-boot-dependencies类中已经指定了这些依赖的版本号。这样做的好处是可以保证所有依赖的版本是协调和兼容的,避免版本冲突的问题。只需要在pom.xml中引入依赖的groupId和artifactId,然后Maven会自动从spring-boot-dependencies中找到对应的版本号进行引入。

Spring Boot启动流程解析


关于spring boot启动流程,我们先说结论:

  1. 创建适当的ApplicationContext实例(取决于你的类路径)
  • ApplicationContext是Spring框架中的一个接口,负责管理Bean以及它们之间的关系。这里的创建适当的ApplicationContext实例意味着根据项目的需求和类路径来选择合适的ApplicationContext实例。
  1. 注册CommandLinePropertySource以将命令行参数作为Spring属性
  • CommandLinePropertySource是Spring框架中用来处理命令行参数的一个类,这个步骤让我们可以通过命令行参数来控制Spring应用的配置信息,例如数据库连接信息或者服务器端口号等。
  1. 刷新应用程序上下文,加载所有单例bean
  • 在Spring框架中,刷新应用程序上下文通常意味着重新读取配置信息并初始化bean。在这个步骤中,所有定义为单例(Singleton)的bean都会被加载到内存中。
  1. 触发任何CommandLineRunner bean
  • CommandLineRunner 是 Spring Boot 提供的一个特殊接口,该接口中只有一个run(String… args)方法,当注入到Spring IOC容器时,Spring会在所有Spring Beans都初始化完成后,以及applicationArguments被调用之后执行这个run方法。

结论来源于SpringApplication类的注释,也就是官方的解释:

pPQ9Zp6.png

我们先看看spring boot的启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

结构很简单,在Application启动类上添加一个@SpringBootApplication,启动类中的main方法中运行SpringApplication的run方法。

我们在运行spring boot程序时也是通过这个main方法进行运行,所以我们先点进去main方法体内执行的run方法。

pPQk3S1.png

然后我们在一步步进入,走到这个run方法内

pPQkJOK.png

此时我们分析一下这段代码

run方法解析


首先这个run方法获取了一下系统启动的时间(单位为纳秒)

long startTime = System.nanoTime();

创建了引导上下文

DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;

又配置了Headless属性

如果在系统属性或环境变量中设置了spring.main.web-environment的值为false,则JVM将以无头(headless)模式运行

configureHeadlessProperty();

又获取了一个监听器并设置了监听器的starting事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);

创建应用参数对象、准备环境变量以及配置忽略BeanInfo

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);

再打印横幅信息,也就是下面这个信息

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::      (v2.7.15-SNAPSHOT)
Banner printedBanner = printBanner(environment);

这时候我们创建应用程序上下文并设置设置应用程序启动方式,以及准备上下文,设置必要的环境参数

context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

一切准备完毕后,刷新上下文,加载Bean等操作

refreshContext(context);
afterRefresh(context, applicationArguments);

这时候我们spring boot基本是启动完毕了,再计算启动花费的时间

Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
	new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),timeTakenToStartup);
}

触发已经启动的监听事件

listeners.started(context, timeTakenToStartup);

调用所有的运行者

callRunners(context, applicationArguments);

计算直至准备完毕所花费的时间并触发准备完毕的监听事件

Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);

最后返回应用程序上下文

return context; 

结论


接下来我们会对上述流程进行更详细的深度分析。

尤其是,run方法中createApplicationContext(), prepareContext(), refreshContext(), afterRefresh()和callRunners()这几个重要过程需要详细解读。

首先,createApplicationContext()根据设置创建相应的应用程序上下文,主要有两种类型,分别是AnnotationConfigServletWebServerApplicationContext和AnnotationConfigReactiveWebServerApplicationContext,前者用于常规的servlet web环境,后者用于反应式web环境。

prepareContext()负责准备上下文,这个方法主要完成了以下三件事情:添加converters(转换器)和generic converters(泛型转换器),将applicationArguments和printedBanner添加进bean工厂,以及调用Initializers初始化上下文。

然后是refreshContext(),它主要负责刷新上下文,例如加载或者刷新配置,创建单例等等。简而言之,就是让Spring容器达到可用状态。

afterRefresh()是在刷新上下文之后才被调用的,在这个方法中会执行源码中所定义的所有Runnable类。

最后是callRunners(),在这个方法中,会调用所有实现ApplicationRunner和CommandLineRunner接口的run方法。这两个接口通常在需要在应用启动后立即执行某些代码时使用,如数据库初始化、加载缓存数据等。


以上就是关于我对run方法的一个解析,这时候大伙会发现,注释中的启动流程与实际代码的流程顺序不一样。

注释中说明的启动流程:

创建ApplicationContext实例 -> 注册CommandLinePropertySource -> 刷新应用程序上下文,加载所有单例bean -> 触发任何CommandLineRunner bean

在spring boot源码中发现,实际的启动流程是:

创建应用参数对象、准备环境变量 -> 创建应用程序上下文 -> 刷新上下文,加载Bean -> 调用所有的运行者

对应语句也就是:

注册CommandLinePropertySource -> 创建ApplicationContext实例 -> 刷新应用程序上下文,加载所有单例bean -> 触发任何CommandLineRunner bean

以下是我个人的理解与看法

虽然在代码中,“创建适当的ApplicationContext实例(取决于你的类路径)”(“context = createApplicationContext();”)确实是出现在"注册CommandLinePropertySource以将命令行参数作为Spring属性"(prepareEnvironment方法)之后,但这并不代表它们的执行顺序就一定要按照这个书写顺序来。

首先,"注册CommandLinePropertySource以将命令行参数作为Spring属性"这个操作是在准备环境变量阶段进行的,其核心目的是为了把命令行参数转化为Spring属性,从而供之后的操作使用。这些属性可能会影响到 ApplicationContext 的创建,比如是否启动web环境、是否开启特定的自动配置等,因此虽然在代码上看起来是在创建ApplicationContext之后,但是这只是声明和初始化,并没有开始使用,ApplicationContext在实际使用之前,我们需要保证其运行的环境已经准备好,所以在逻辑层面,我们可以认为"注册CommandLinePropertySource以将命令行参数作为Spring属性"这个步骤是在"创建适当的ApplicationContext实例"之前完成的。

另外,Spring Boot的设计原则之一是“约定优于配置”,在大多数情况下,Spring Boot能根据类路径自动推断出你想要创建的ApplicationContext实例类型,在某种程度上,这也降低了创建ApplicationContext的复杂性。

因此,虽然代码书写顺序上看起来有些混乱,但在实际逻辑处理上,并没有违反Spring Boot的启动流程的说明。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值