Spring Boot 启动流程分析
Spring Boot 简化了基于 Spring 的应用程序的创建和部署过程,其启动流程涉及多个步骤和组件。为了详细理解 Spring Boot 的启动流程,我们可以从源码的角度进行分析。以下是 Spring Boot 启动过程的详细介绍,结合关键源码类和方法进行说明。
应用程序的入口点
通常,Spring Boot 应用程序的启动入口是一个带有 main
方法的类,该类使用 @SpringBootApplication
注解。例如:
1 |
|
关键源码
SpringApplication.run
方法SpringApplication.run
是启动 Spring Boot 应用程序的主要方法。其源码位于org.springframework.boot.SpringApplication
类中。
创建 SpringApplication
实例
当调用 SpringApplication.run
时,内部实际上是创建了一个 SpringApplication
的实例,并调用其 run
方法。
1 | public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { |
关键源码
SpringApplication
构造函数负责初始化应用程序的基本设置,包括检测是否为 Web 应用、设置默认属性等。豆腐浆肯德基疯狂
豆腐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 用于加载资源的接口,可以是类路径资源或文件系统资源
this.resourceLoader = resourceLoader;
// 主要的源类,通常是包含 @SpringBootApplication 注解的类。
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断 Web 应用类型:根据类路径中的依赖项推断应用程序的类型(例如,是否是 Web 应用程序)。
// 这会检查是否存在特定的类(如 Servlet、Reactive 等)。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置应用初始化器:从 Spring 工厂中加载所有 ApplicationContextInitializer 的实例,并将其设置到应用程序中。
// 初始化器用于在应用上下文刷新之前进行配置。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置应用监听器:同样,从 Spring 工厂中加载所有 ApplicationListener 的实例,并将其设置到应用程序中。
// 监听器用于响应应用程序事件。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断主应用类:通过分析 primarySources 确定主应用类(通常是包含 main 方法的类),这对配置和后续操作非常重要。
this.mainApplicationClass = deduceMainApplicationClass();
}其中,
getSpringFactoriesInstances
方法会调用静态方法SpringFactoriesLoader.loadFactoryNames
,该方法读取META-INF/spring.factories
, 获取各种自动配置类,如 Initializers、Application Listeners、Import Listeners、Import Filters、Auto Configure、Failure analyzers、Template availability providers。在这里,主要提前获取 ApplicationContextInitializer 和 ApplicationListener 的实例。
准备 SpringApplication
实例
在 run
方法中,SpringApplication
实例会经过一系列的准备工作,例如绑定命令行参数、设置环境等。
1 | public ConfigurableApplicationContext run(String... args) { |
关键源码
prepareEnvironment
方法负责创建和配置
Environment
对象,包括加载默认的配置属性源。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private ConfigurableEnvironment prepareEnvironment() {
// 创建StandardEnvironment或者StandardServletEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 增加属性源,例如命令行参数、系统属性等
configureEnvironment(environment);
// 设置启动参数
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 再设置一次
ConfigurationPropertySources.attach(environment);
return environment;
}createApplicationContext
方法根据应用类型(如 Web 应用或非 Web 应用),
SpringApplication
会创建不同类型的ApplicationContext
。对于 Web 应用,通常是AnnotationConfigServletWebServerApplicationContext
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}prepareContext
方法将环境配置、命令行参数、启动横幅等信息整合到应用上下文中,并通过监听器通知各个阶段的准备情况。这一过程确保了上下文的正确配置,并为后续的上下文刷新和 Bean 初始化做好了准备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
// 后处理上下文:调用 postProcessApplicationContext 方法,
// 可以进行自定义的处理,比如添加额外的配置或 Bean 定义。
postProcessApplicationContext(context);
// 应用初始化器:调用所有注册的 ApplicationContextInitializer,这些初始化器可以在上下文刷新之前对其进行配置。
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 添加 Boot 特殊的单例 beans,
// 获取应用上下文的 BeanFactory,这个工厂负责管理 Bean 的生命周期和依赖关系。
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注册应用参数:将 applicationArguments 作为单例 Bean 注册到 BeanFactory,
// 以便在应用程序中可以通过依赖注入访问。
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
// 注册横幅:如果存在打印的横幅,将其作为单例 Bean 注册到 BeanFactory,以便在应用程序中使用。
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 允许 Bean 定义覆盖:如果 BeanFactory 是 DefaultListableBeanFactory 的实例,
// 设置是否允许 Bean 定义的覆盖。这允许用户在特定情况下定义相同名称的 Bean。
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 添加懒加载处理器:如果设置了懒加载,则向上下文添加 BeanFactoryPostProcessor,
// 以便在 Bean 被请求时才初始化它们。
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 获取所有源:调用 getAllSources 方法获取所有源(例如配置类、配置文件等),并确保这些源不为空。
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载源:将所有源加载到上下文中。这一步主要负责注册 Bean 定义、执行配置等。
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}applyInitializers
方法在prepareContext方法中,调用
applyInitializers
方法,应用所有注册的ApplicationContextInitializer
,允许在上下文刷新之前自定义上下文。1
2
3
4
5private void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : this.initializers) {
initializer.initialize(context);
}
}load
方法在prepareContext方法中,调用
load
方法,将源中的 Bean 定义加载到应用上下文中,并配置相关的加载器参数。这一过程确保了 Spring 应用程序能够根据给定的配置正确地初始化和管理所有的 Bean。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
刷新上下文
SpringApplication
利用 SpringFactoriesLoader
加载所有的 ApplicationContextInitializer
、ApplicationListener
以及 EnvironmentPostProcessor
,然后刷新应用上下文,触发 Spring 的 Bean 加载和初始化过程。
关键源码
refreshContext
方法负责刷新应用上下文,包括触发 Spring 的生命周期回调,如 Bean 的加载、处理
@Configuration
注解等。1
2
3
4
5
6
7
8
9
10
11private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
refresh((ApplicationContext) context);
}refresh
方法1
2
3
4protected void refresh(ConfigurableApplicationContext applicationContext) {
// 调用 AbstractApplicationContext 的 refresh 方法
applicationContext.refresh();
}
触发自动配置
Spring Boot 的自动配置是通过 @EnableAutoConfiguration
注解实现的。AutoConfigurationImportSelector
会根据应用的依赖和 Environment
的属性选择性地导入配置类。
关键源码
AutoConfigurationImportSelector
类在
invokeBeanFactoryPostProcessors
阶段执行, 负责选择需要导入的自动配置类。1
2
3
4
5
6
7public class AutoConfigurationImportSelector extends ConfigurationClassImportSelector
implements BeanFactoryAware, EnvironmentAware {
protected AnnotationMetadata getAnnotationMetadata() {
return AnnotationMetadata.introspect(EnableAutoConfiguration.class);
}
}spring.factories
文件在
META-INF/spring.factories
中,EnableAutoConfiguration
会指定所有可用的自动配置类。1
2
3
4org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...
处理 CommandLineRunner
和 ApplicationRunner
在应用上下文刷新完成后,SpringApplication
会调用所有的 CommandLineRunner
和 ApplicationRunner
接口的实现,用于在应用启动后执行特定的逻辑。
关键源码
callRunners
方法1
2
3
4
5
6
7
8
9private void callRunners(ApplicationContext context, ApplicationArguments args) {
if (this.mainApplicationClass != null) {
Class<?> runnerType = CommandLineRunner.class;
Collection<CommandLineRunner> runners = context.getBeansOfType(runnerType).values();
for (CommandLineRunner runner : runners) {
runner.run(args.getSourceArgs());
}
}
}
启动 Web 服务器(如果是 Web 应用)
对于 Web 应用,Spring Boot 会自动启动嵌入式的 Web 服务器(如 Tomcat、Jetty 或 Undertow)。
启动流程:
在 AbstractApplicationContext的refresh的onRefresh阶段,会触发ServletWebServerApplicationContext的onRefresh方法,会去创建ServletWebServerFactory。最后,在 AbstractApplicationContext的refresh的最后阶段 finishRefresh() 时,会被触发 start方法,启动web服务器。
关键源码
WebServerStartStopLifecycle
类,他继承了SmartLifecycle
,故能自动start与stop。他内部持有WebServer
和ServletWebServerApplicationContext
。1
2
3
4
5
6
7
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}ServletWebServerApplicationContext
类管理 Web 服务器的生命周期。
1
2
3
4public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
// 实现细节
}
总结
Spring Boot 的启动流程涉及以下主要步骤:
- 启动入口:通过调用
SpringApplication.run
启动应用。 - 环境准备:创建并配置
Environment
,加载配置属性。 - 应用上下文创建:根据应用类型创建合适的
ApplicationContext
实例。 - 准备上下文:将环境配置、命令行参数、启动横幅等信息整合到应用上下文中,并通过监听器通知各个阶段的准备情况。
- 刷新上下文:触发 Spring 的 Bean 加载和初始化过程。
- 执行 Runner:调用
CommandLineRunner
和ApplicationRunner
。 - 启动 Web 服务器:如果是 Web 应用,启动嵌入式服务器。
通过以上步骤,Spring Boot 能够快速且自动化地启动和配置一个基于 Spring 的应用程序。理解这些流程和相关的源码,对于深入掌握 Spring Boot 的工作机制以及进行高级定制具有重要意义。