引言
欢迎阅读本篇文章,本文将深入探讨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那样手动配置一大堆东西。这种特性大大简化了开发流程,让我们更专注于业务代码的编写。
以下是官方文档解释:
根据官方文档附录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父项目之后,再去引入某些依赖时,发现并不需要指定版本号。
我们按住Ctrl,点进去spring-boot-starter-parent父项目pom文件内,发现他还有一层夫类
这时候我们再点进去他的父类 ‘spring-boot-dependencies’ ,进入他的pom文件。
然后就能发现,在 '‘spring-boot-dependencies’ 类中,已经指定了多个依赖的版本号。
根据**“如果我们在引入依赖时没有指定版本号,那么就默认使用父项目中的版本号”**,我们可以得出一个结论:
在引入springboot父项目后,不需要再指定某些依赖的版本号是因为在spring-boot-dependencies类中已经指定了这些依赖的版本号。这样做的好处是可以保证所有依赖的版本是协调和兼容的,避免版本冲突的问题。只需要在pom.xml中引入依赖的groupId和artifactId,然后Maven会自动从spring-boot-dependencies中找到对应的版本号进行引入。
Spring Boot启动流程解析
关于spring boot启动流程,我们先说结论:
- 创建适当的ApplicationContext实例(取决于你的类路径)
- ApplicationContext是Spring框架中的一个接口,负责管理Bean以及它们之间的关系。这里的创建适当的ApplicationContext实例意味着根据项目的需求和类路径来选择合适的ApplicationContext实例。
- 注册CommandLinePropertySource以将命令行参数作为Spring属性
- CommandLinePropertySource是Spring框架中用来处理命令行参数的一个类,这个步骤让我们可以通过命令行参数来控制Spring应用的配置信息,例如数据库连接信息或者服务器端口号等。
- 刷新应用程序上下文,加载所有单例bean
- 在Spring框架中,刷新应用程序上下文通常意味着重新读取配置信息并初始化bean。在这个步骤中,所有定义为单例(Singleton)的bean都会被加载到内存中。
- 触发任何CommandLineRunner bean
- CommandLineRunner 是 Spring Boot 提供的一个特殊接口,该接口中只有一个run(String… args)方法,当注入到Spring IOC容器时,Spring会在所有Spring Beans都初始化完成后,以及applicationArguments被调用之后执行这个run方法。
结论来源于SpringApplication类的注释,也就是官方的解释:
我们先看看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方法。
然后我们在一步步进入,走到这个run方法内
此时我们分析一下这段代码
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的启动流程的说明。