Spring Boot启动加载顺序详解

2023-08-23 10:57:21 浏览数 (2)

Spring Boot应用的启动过程看似简单,但其中涉及了复杂的初始化和加载机制。本文将深入剖析Spring Boot的启动流程,了解其自动配置、引导启动和源码运行等 every detail。

一、整体启动流程

当我们通过java -jar命令启动Spring Boot应用时,整个启动过程经历了以下关键步骤:

  1. 装载核心启动器类:org.springframework.boot.SpringApplication
  2. 运行SpringApplication的静态run方法,传入主配置类
  3. 实例化SpringApplication对象,加载应用上下文初始化器
  4. 准备并刷新应用上下文Context
  5. 触发所有CommandLineRunner执行
  6. 启动完成,等待退出

接下来我们重点看一下启动的源码流程和自动配置机制。

二、SpringApplication启动流程剖析

SpringApplication类提供了一站式服务来引导启动整个Spring Boot程序,其中封装了很多启动时的初始化和加载逻辑。

1. 初始化启动类

通过构造函数创建SpringApplication实例时,进行了一系列的初始化工作:

  • 判断并设置web环境类型:SERVLETREACTIVE
  • 使用SpringFactoriesLoader加载ApplicationContextInitializerApplicationListener
  • 推断并设置主配置类 primary sources
代码语言:javascript复制
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
  ...
  this.webApplicationType = WebApplicationType.deduceFromClasspath();
  this.setInitializers(this.getSpringFactoriesInstances();
  this.setListeners(this.getSpringFactoriesInstances());
  this.mainApplicationClass = this.deduceMainApplicationClass();
}

这样就创建好了一个可执行的SpringApplication实例。

2. 开始执行run方法

接着调用run(args)方法启动整个Spring Boot程序:

代码语言:javascript复制
public ConfigurableApplicationContext run(String... args) {
   // 停止watch服务
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   // 声明应用上下文和异常报告集合
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters;
   // 头部打印Banner
   configureHeadlessProperty();
   // 获取SpringApplicationRunListeners,用于监听run方法的整个流程
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 启动所有的监听器
   listeners.starting();
   try {
      // 封装命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      // 准备环境 Environment 
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
        ......
   }
   catch (Throwable ex) {
     ......
   }
   // 完成时长记录
   stopWatch.stop();
   if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
   }
   // 发布应用启动完成事件
   listeners.started(context);
   // 执行所有 runners
   callRunners(context, applicationArguments);
}

run方法主要完成了以下工作:

  • 构建应用启动监听器 SpringApplicationRunListeners
  • 封装并准备环境Environment
  • 创建并配置应用上下文ApplicationContext
  • 回调ApplicationContext初始化器initializers
  • 最后一步 refresh 应用上下文使其完成加载

在这段代码中,我们可以看到启动的关键步骤都出现了,包括监听器、环境、应用上下文的准备,其中隐含了复杂的加载机制。

3. 创建应用上下文

SpringApplication会根据web环境类型创建响应的应用上下文对象,常见的两种:

  • AnnotationConfigServletWebServerApplicationContext:web环境
  • AnnotationConfigApplicationContext:普通应用
代码语言:javascript复制
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);
}

//利用反射实例化上下文 
context = (ConfigurableApplicationContext) 
     contextClass.getConstructor(ApplicationContext.class)
          .newInstance(this.applicationContext); 

可以看到Spring Boot会通过应用类型选择合适的ApplicationContext来实例化,保证后续组件的加载顺利进行。

4. 准备应用上下文

在获得ApplicationContext实例后,Spring Boot会继续对其进行准备工作,主要在 prepareContext()方法中:

  • 将命令行参数添加到 Environment 中
  • 应用ApplicationContextInitializer初始化器到上下文
  • 加载主配置类信息 primarySources 到上下文
  • 触发监听器的 contextPrepared 事件

这样一系列的准备工作完成了对上下文环境的构建和初始化,为后续的 refresh 提供基础。

5. 刷新应用上下文

最后通过调用 refresh() 方法启动整个应用上下文:

代码语言:javascript复制
private void refreshContext(ConfigurableApplicationContext context) {
  refresh(context);
  if (this.registerShutdownHook) {
    try {
      context.registerShutdownHook();
    }
    catch (AccessControlException ex) {
    }
  }
} 

public void refresh() throws BeansException, IllegalStateException {
   // 初始化剩余的bean定义
   finishBeanFactoryInitialization(beanFactory);

   // 最后一步:发布容器刷新完成事件
   finishRefresh();
}

在这里,容器会逐步完成Bean定义的加载、注入依赖、初始化等工作,是启动过程中最关键的部分。

至此,SpringApplication的执行流程我们就解析完毕了,可以看到其包含了初始化、监听、载入、刷新等多个关键步骤,将应用启动的复杂过程进行了串联和统一。这就是Spring Boot应用启动的整体流程。

三、自动配置流程解析

除了启动流程,Spring Boot中还包含了强大的自动配置功能,这也是其魅力所在。那么Spring Boot又是如何实现自动配置的呢?

1. @EnableAutoConfiguration

在Spring Boot主配置类上,通常会通过 @EnableAutoConfiguration 注解开启自动配置:

代码语言:javascript复制
@Configuration
@EnableAutoConfiguration
public class MyApplication { }  

这里的 @EnableAutoConfiguration 实现了自动配置的开关。

2. AutoConfigurationImportSelector

@EnableAutoConfiguration的关键在于向容器导入了一个 AutoConfigurationImportSelector 的组件,它实现了 DeferredImportSelector接口。

在其中 getAutoConfigurationEntry() 方法包含了自动配置的核心;

3. 加载自动配置类

AutoConfigurationImportSelector通过getAutoConfigurationEntry()方法加载自动配置类:

代码语言:javascript复制
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  // 获取所有自动配置类
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  
  // 去重并过滤
  configurations = removeDuplicates(configurations);
  
  // 封装并返回配置类
  return new AutoConfigurationEntry(configurations, exclusions);
}

这里主要通过Spring Factories Loader机制加载META-INF/spring.factories中的自动配置类实现EnableAutoConfiguration。

4. 自动配置类生效

上一步加载了大量的自动配置类,但并不是所有配置都会生效,这就涉及到了条件装配。

每一个自动配置类中都定义了 @Conditional 条件注解,只有当条件匹配才会将配置添加到上下文中。

例如 @ConditionalOnClass 指定的类必须存在,@ConditionalOnMissingBean对应类型的Bean必须不存在等。

这些条件实现了自动配置的精确控制,避免引入不必要的组件。

代码语言:javascript复制
@Configuration
@ConditionalOnClass(DataSource.class) 
public class DataSourceAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public DataSource dataSource() {
    // ...
  }

}

Spring Boot通过条件装配实现了自动配置的优雅适配。

总结

至此,我们剖析完了Spring Boot应用启动和自动配置的整个过程,可以看到其内部对容器上下文环境的构建进行了精心设计,使得应用能够顺利启动并加载所需的Bean。这种自动配置编程模型是Spring Boot最耀眼的设计之一。

0 人点赞