Spring boot启动过程详解

程序设计的所有原则和方法论都是追求一件事------简单------功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。

概要

Spring boot为spring集成开发带来很大的遍历,降低了spring中bean的配置工作,几乎0配置即可开发一个spring应用。本篇主要介绍spring boot在启动过程中都做了哪些工作,重点介绍在spring容器ApplicationContext刷新前都做了哪些准备,本篇以源码解释为主,示例为辅进行梳理其过程。

Spring boot整个启动过程分为3部分,分别是

  1. 准备环境配置
  2. 准备Spring容器并刷新容器
  3. 容器刷新后处理

每个部分又有如下图所示的步骤(图片比较宽,左右滑动查看):
3.容器刷新后处理 3.1发布ApplicationStartedEvent事件 3.2执行ApplicationRunner和CommandLineRunner 3.3发布ApplicationReadyEvent事件 2.Spring容器准备和刷新 2.1创建Spring容器ConfigurableApplicationContext 2.2执行ApplicationContextInitializer初始化Spring容器 2.3容器已准备好,发布ApplicationContextInitializedEvent事件标识容器已初始化 2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件 2.5加载主要入口类primarySources的BeanDefinition 2.6发布ApplicationPreparedEvent事件 2.7刷新容器 1.环境配置准备 1.1创建SpringApplication并执行其run方法 1.2创建DefaultBootstrapContext并执行BootstrapRegistryInitializer 1.3从spring.factories文件加载所有SpringApplicationRunListener 1.4发布ApplicationStartingEvent事件标识正在启动应用 1.5创建ConfigurableEnvironment加载环境配置如环境变量和系统配置以及命令行参数 1.6发布ApplicationEnvironmentPreparedEvent事件标识环境配置已准备好

以上步骤过程主要在SpringApplication对象的`run方法中执行,该方法是一个模板模式的应用,定义了spring boot应用的启动过程,源码如下,注意阅读注释

java 复制代码
// spring boot SpringApplication源码
public ConfigurableApplicationContext run(String... args) {
    Startup startup = Startup.create();
    if (this.registerShutdownHook) { // 默认为true
        SpringApplication.shutdownHook.enableShutdownHookAddition();
    }
    // 1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializer
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    //1.3从spring.factories文件加载所有SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 1.4发布ApplicationStartingEvent事件标识正在启动应用
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 命令行参数对象化
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 1.5和1.6 加载环境配置和发布ApplicationEnvironmentPreparedEvent事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        // 打印spring banner
        Banner printedBanner = printBanner(environment);

        // 2.1创建Spring容器ConfigurableApplicationContext
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 2.2-2.6
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 2.7刷新容器
        refreshContext(context);
        // 刷新后处理器,钩子函数,空实现,留给子类实现
        afterRefresh(context, applicationArguments);
        startup.started();
        if (this.logStartupInfo) { // 默认为true
            // 打印启动耗时
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
        }

        // 3.1发布ApplicationStartedEvent事件
        listeners.started(context, startup.timeTakenToStarted());
        // 3.2执行ApplicationRunner和CommandLineRunner
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        throw handleRunFailure(context, ex, listeners);
    }
    try {
        if (context.isRunning()) {
            // 3.3发布ApplicationReadyEvent事件
            listeners.ready(context, startup.ready());
        }
    } catch (Throwable ex) {
        throw handleRunFailure(context, ex, null);
    }
    return context;
}

下面根据上面代码注释上的编号逐一梳理,注释前的编号与下面的章节号对应,本篇代码中的注释前编号都是与之对应的章节号。

1. 环境配置准备

1.1 创建SpringApplication并执行其run方法

1.1.1 创建SpringApplication

SpringApplication类大家都不陌生,它是spring boot提供的外观类,屏蔽了spring庞杂的内部结构,通过它只需要一行代码即可启动一个spring

boot应用。它在启动时有3中创建方式:

  1. 执行其静态方法run方法,这是我们经常使用的方式。
java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
        // 等同于
        // new SpringApplication(MyApplication.class).run(args);
    }
}
  1. 通过new关键字直接创建
java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setAllowCircularReferences(true);
        application.setAllowBeanDefinitionOverriding(true);
        application.setLogStartupInfo(true);
        application.setWebApplicationType(WebApplicationType.SERVLET);
        application.run(args);
    }

}
  1. 使用SpringApplicationBuilder
java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(MyApplication.class)
            .allowCircularReferences(true)
            .logStartupInfo(true)
            .web(WebApplicationType.REACTIVE)
            .run();
    }
}

3种方式简要说明:

  1. 第一种方式通过执行静态方法,非常简单,也是我们经常使用的方式
  2. 后面两种给予我们除了配置文件外,还可以通过代码来控制启动时需要的配置。如果你喜欢使用链式编程风格,你可以选择第三种方式。

1.1.2 创建SpringApplication

下面是SpringApplication构造函数源码,注意阅读注释,后面根据注释的编号做进一步分析。

java 复制代码
// spring boot SpringApplication源码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 用于加载资源,默认为null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 即被@SpringBootApplication注解的类,以它为根进行扫描所有bean定义
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 1.1.2.1 判断应用类型,分别是SERVLET|REACTIVE|NONE。后面会根据应用类型创建Spring容器
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 1.1.2.2 加载spring.factories中配置的扩展实现类
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 1.1.2.3 main方法所在类,后面用于日志显示
    this.mainApplicationClass = deduceMainApplicationClass();
}

代码说明见下面3节

1.1.2.1 判断应用类型

应用类型WebApplicationType

有3个枚举值,分别是SERVLET|REACTIVE|NONE。不同类型会选择相应的抽象工厂ApplicationContextFactory

工厂方法创建spring容器对象。下面表格是抽象工厂各实现提供的容器类和环境对象类。

枚举 Factory实现类 AOT编译时的容器类 非AOT时的容器类 提供的Environment实现类
SERVLET ServletWebServerApplicationContextFactory ServletWebServerApplicationContext AnnotationConfigServletWebServerApplicationContext ApplicationServletEnvironment
REACTIVE ReactiveWebServerApplicationContextFactory ReactiveWebServerApplicationContext AnnotationConfigReactiveWebServerApplicationContext ApplicationReactiveWebEnvironment
NONE DefaultApplicationContextFactory GenericApplicationContext AnnotationConfigApplicationContext ApplicationEnvironment

spring boot从3.0开始支持GraalVM Native编译即AOT编译,AOT编译要求所有代码都是可达、静态的,通过注解在运行时动态加载具体类型违背AOT的原则,

Spring boot为支持AOT编译需要提前为通过注解配置的bean生成代码和AOP时的字节码,比如下面为示例的启动类MyApplication生成的代码。

因此AOT的容器类不需支持加载注解配置,而只需要执行java代码就可以装载bean。

java 复制代码
// 示例代码
@Generated
public class MyApplication__BeanDefinitions {

    /**
     * Get the bean definition for 'MyApplication'.
     */
    public static BeanDefinition getMyApplicationBeanDefinition() {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyApplication.class);
        beanDefinition.setTargetType(MyApplication.class);
        ConfigurationClassUtils.initializeConfigurationClass(MyApplication.class);
        beanDefinition.setInstanceSupplier(MyApplication$$SpringCGLIB$$0::new);
        return beanDefinition;
    }
}
1.1.2.2 加载spring.factories中配置的扩展实现类
  1. BootstrapRegistryInitializer : 用于向BootstrapRegistry中注册对象,在创建BootstrapRegistry后就会执行,见后面1.2章节
  2. ApplicationContextInitializer: 用于向ConfigurableApplicationContextspring容器添加对象,在执行refresh()方法前触发,见[2.2 执行ApplicationContextInitializer初始化Spring容器](#2.2 执行ApplicationContextInitializer初始化Spring容器)。
  3. ApplicationListener : 这里注册的监听器可以监听spring boot应用的整个过程的事件
1.1.2.3 其它
  1. resourceLoader默认为null,
  2. primarySources为spring加载业务代码bean的起点,通常为被@SpringBootApplication注解的类,也可以是其它spring注解的类。
  3. mainApplicationClass为main方法所在类

1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializer

SpringApplication源码如下:

java 复制代码
// spring boot SpringApplication源码
private DefaultBootstrapContext createBootstrapContext() {
    // 1.2.1 创建启动上下文
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
    // 1.2.2 执行BootstrapRegistryInitializer
    this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
    return bootstrapContext;
}

1.2.1 创建启动上下文

DefaultBootstrapContext实现了BootstrapRegistryBootstrapContext两个接口,前者用于注册启动相关的类对象,后者用于获取注册的类对象。

1.2.2 执行BootstrapRegistryInitializer

BootstrapRegistryInitializer通过spring.factories文件注册,一般用于往BootstrapRegistryDefaultBootstrapContext注册自定义框架的对象,用在后面某些步骤中发挥作用。

1.3从spring.factories文件加载所有SpringApplicationRunListener

SpringApplicationRunListener用于监听SpringApplication启动过程的特定步骤,它定义的方法与启动步骤一一对应。
SpringApplication中对应的源码如下,注意阅读注释,后面根据注释的编号做进一步分析。

java 复制代码
// spring boot SpringApplication源码
private SpringApplicationRunListeners getRunListeners(String[] args) {
    // 1.3.1 从spring.factories加载SpringApplicationRunListener
    ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
    argumentResolver = argumentResolver.and(String[].class, args);
    List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
        argumentResolver);

    // 1.3.2 从ThreadLocal中获取应用钩子
    SpringApplicationHook hook = applicationHook.get();
    SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;
    if (hookListener != null) {
        // 把钩子提供的监听器执行器加入listeners
        listeners = new ArrayList<>(listeners);
        listeners.add(hookListener);
    }
    // 1.3.3 创建一个外观对象来编排各个监听执行器的执行
    return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}

1.3.1 从spring.factories加载SpringApplicationRunListener

在上面源码中有2个点要注意:

  1. spring提供的默认实现为EventPublishingRunListener,spring boot启动过程中的各类ApplicationEvent
    事件(如ApplicationStartingEvent事件)都由它告知所有ApplicationListener对象。
  2. SpringApplicationRunListener实现类构造函数可选参数有:无参、SpringApplication,命令行参数String[] args,如下面代码
java 复制代码
// 示例代码
public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private final SpringApplication application;

    public MySpringApplicationRunListener(SpringApplication application) {
        this.application = application;
    }
    // 其它代码...
}

关于SpringApplicationRunListener需说明如下:

SpringApplicationRunListenerApplicationListener一样都属于监听器(观察者),不过它用于观察spring boot的启动过程,
SpringApplication在各个阶段通过其管理器SpringApplicationRunListeners来执行各个SpringApplicationRunListener

对象的阶段函数(如startingstartedready)。

1.3.2 从ThreadLocal中获取应用钩子

springboot3.0提供了启动应用程序可以设置钩子,由这个钩子提供一个SpringApplicationRunListener对象,比如下面代码

java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.withHook(new MySpringApplicationHook(), () -> {
            SpringApplication.run(MyApplication.class, args);
        });
    }
}

public class MySpringApplicationHook implements SpringApplicationHook {

    @Override
    public SpringApplicationRunListener getRunListener(SpringApplication springApplication) {
        return new MySpringApplicationRunListener(springApplication);
    }
}

spring boot提供这个钩子功能的作用:

  1. AOT编译使用该钩子在spring容器刷新前抛出异常来终止动态类扫描,从而做到不启动应用。因此使用AOT编译的程序,不可在spring容器刷新前使用非守护

    线程做长时间处理,否则影响编译时间。

  2. SpringApplication应用监听器隔离

1.3.3 创建一个外观对象来编排各个监听执行器的执行

SpringApplicationRunListeners是个外观类,它提供了与SpringApplicationRunListener一致的方法,在发布事件时会做两件事:

  1. 对所有监听执行器做迭代执行
  2. 记录执行步骤

见下面SpringApplicationRunListeners关于spring boot开始时发布第一个事件的源码

java 复制代码
// spring boot源码
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
        (step) -> {
            if (mainApplicationClass != null) {
                step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
        });
}

private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
    Consumer<StartupStep> stepAction) {
    StartupStep step = this.applicationStartup.start(stepName);
    // 执行所有监听执行器
    this.listeners.forEach(listenerAction);
    if (stepAction != null) {
        stepAction.accept(step);
    }
    step.end();
}

1.4发布ApplicationStartingEvent事件标识正在启动应用

接上面SpringApplicationRunListeners关于spring

boot开始时发布第一个事件的源码,接下来执行默认的springboot事件发布监听器EventPublishingRunListener,

方法调用顺序:SpringApplicationRunListeners#starting --> EventPublishingRunListener#starting,

下面是其源码

java 复制代码
// spring boot EventPublishingRunListener源码
EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
}

public void starting(ConfigurableBootstrapContext bootstrapContext) {
    multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

private void multicastInitialEvent(ApplicationEvent event) {
    // 1.4.1 每次发布事件都更新监听器列表,保障每步中新加监听器能收到事件
    refreshApplicationListeners();
    // 1.4.2 使用多播器分发事件
    this.initialMulticaster.multicastEvent(event);
}

private void refreshApplicationListeners() {
    this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}

1.4.1 更新ApplicationListener监听器列表

在spring boot的启动各阶段,都有可能往SpringApplication添加监听器,为了保障新加监听器能收到事件,每次都要刷新多播器中的监听器列表。

如下面的示例代码,如果应用类型为REACTIVE则添加一个监听器:

java 复制代码
// 示例代码
public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private final SpringApplication application;

    public MySpringApplicationRunListener(SpringApplication application) {
        this.application = application;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        if (application.getWebApplicationType().equals(WebApplicationType.REACTIVE)) {
            application.addListeners(event -> {
                System.out.println("REACTIVE APP trigger event: " + event);
            });
        }
    }
}

1.4.2 使用多播器分发事件

这里使用的多播器和Spring容器默认使用的多播器属于同一个类即SimpleApplicationEventMulticaster,该类在实现上不单是维护一个监听器列表。

在Spring事件机制的实现中,ApplicationEventMulticaster多播器是事件机制的外观类,可以根据ApplicationEvent事件对象匹配适合的监听器,从而实现监听器之间的独立性。

1.5创建ConfigurableEnvironment加载环境配置如环境变量和系统配置以及命令行参数

准备环境配置的流程如下,注意阅读注释,后面根据注释的编号做进一步分析。

java 复制代码
// spring boot SpringApplication源码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // 1.5.1 创建环境对象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 1.5.2 配置环境对象
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 把所有source使用ConfigurationPropertySourcesPropertySource来管理,
    ConfigurationPropertySources.attach(environment);
    // 1.5.3 通知监听器环境对象以准备好
    listeners.environmentPrepared(bootstrapContext, environment);
    // 把默认配置(名称为:defaultProperties)移到最后,表示被使用的优先级最低
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
        "Environment prefix cannot be set via properties.");
    // 1.5.4 通过设置以spring.main为前缀的配置来设置SpringnativeApplication对象的属性
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        // 1.5.5 如果是自定义环境类型,则转为应用类型相符的环境类型
        EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
        // deduceEnvironmentClass方法根据应用类型返回对应的环境类型
        // 把environment转换为应用对应的环境类型对象 
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    // 把所有source使用ConfigurationPropertySourcesPropertySource来管理,
    ConfigurationPropertySources.attach(environment);
    return environment;
}

1.5.1 创建环境对象

java 复制代码
// spring boot SpringApplication源码
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
    if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
        environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
    }
    return (environment != null) ? environment : new ApplicationEnvironment();
}

上面代码是SpringApplication创建环境对象的代码面代码,如果没有对SpringApplication对象设置环境对象,则使用ApplicationContextFactory

对象来创建环境对象,详细说明如下。

1. 确定环境对象类型

在该步骤中,spring boot根据应用类型WebApplicationType选择可以支持的ApplicationContextFactory抽象工厂的实现类,

通过该抽象工厂实现类来创建ConfigurableEnvironment对象。下表是WebApplicationType对应的环境对象

WebApplicationType枚举 ApplicationContextFactory实现类名 提供的Environment实现类
SERVLET ReactiveWebServerApplicationContextFactory org.springframework.boot.web.servlet.context.ApplicationServletEnvironment
REACTIVE ServletWebServerApplicationContextFactory org.springframework.boot.web.reactive.context.ApplicationReactiveWebEnvironment
NONE DefaultApplicationContextFactory org.springframework.boot.ApplicationEnvironment

2. 创建环境对象,并加载初始配置

这个时候会把各种类型的配置装载为PropertySource对象,对于Servlet应用,加载的环境配置类型有:(下面标星*

的是所有应用类型都有)

配置类型名称 说明
servletConfigInitParams servlet应用配置
servletContextInitParams servlet容器上下文配置
jndiProperties JNDI环境配置
*systemProperties 系统属性配置
*systemEnvironment 系统环境变量配置

1.5.2 配置环境对象

给环境对象装载配置主要做3件事情的源码如下

java 复制代码
// spring boot SpringApplication源码
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) { // 默认位处
        // 1. 添加类型解析器,spring内置了很多类型之间的转换和类型的格式化器
        environment.setConversionService(new ApplicationConversionService());
    }
    // 2. 装载默认配置和命令行指定配置
    configurePropertySources(environment, args);
    // 配置profile,(实现为空,留给SpringApplication子类扩展的)
    configureProfiles(environment, args);
}
  1. 默认添加ApplicationConversionService对象作为类型解析器,这个解析器内置了很多类型转换器和格式化器,具体可见该类的源码,比较简单。在开发中也可以用它来做对象转换。

  2. 装载默认配置和命令行指定配置,见下面代码

    1. 如果设置了默认配置,则添加名为defaultPropertiesPropertySource,并添加source列表尾部。
    2. 对于命令行配置,如--server.port=8080, 则添加一个名为commandLineArgsPropertySource,并添加source列表头部。
    java 复制代码
     // spring boot SpringApplication源码
     protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
         MutablePropertySources sources = environment.getPropertySources();
         // i 添加defaultProperties source
         if (!CollectionUtils.isEmpty(this.defaultProperties)) {
             DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
         }
         // ii 添加commandLineArgs source
         if (this.addCommandLineProperties && args.length > 0) {
             String name = "commandLineArgs";
             if (sources.contains(name)) {
                 PropertySource<?> source = sources.get(name);
                 // 组合模式,组合多个命令行配置
                 CompositePropertySource composite = new CompositePropertySource(name);
                 composite .addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                 composite.addPropertySource(source);
                 sources.replace(name, composite);
             } else {
                 sources.addFirst(new SimpleCommandLinePropertySource(args));
             }
         }
     }

SimpleCommandLinePropertySource {name='commandLineArgs'}

1.5.3 通知监听器环境对象以准备好

到这一步了,环境对象该准备的基本配置都准备好了,对于Servlet应用,加载的环境配置类型有:(下面标星*的是所有应用类型都有)

配置类型名称 说明
*configurationProperties attach下面所有配置,通过它可以对所有配置进行很方便的迭代
*commandLineArgs 命令行配置,如java -jar app.jar --server.port=8080
servletConfigInitParams servlet应用配置
servletContextInitParams servlet容器上下文配置
jndiProperties JNDI环境配置
*systemProperties 系统属性配置
*systemEnvironment 系统环境变量配置
*defaultProperties 默认配置,如果通过setDefaultProperties()方法指定了则有

这个时候通过下面3种方式执行扩展的业务逻辑

  1. 实现SpringApplicationRunListener监听器
  2. 实现ApplicationListener监听ApplicationEnvironmentPreparedEvent事件,如LoggingApplicationListener会监听到该事件时加载日志配置。
  3. 实现EnvironmentPostProcessor接口,
    该接口的内置实现如下表,所有实现类都由EnvironmentPostProcessorApplicationListener监听器加载并执行。
实现类 说明
RandomValuePropertySourceEnvironmentPostProcessor 提供随机值配置,以random.为前缀的配置值获取,比如随机int: random.int,随机long: random.long,随机uuid: random.uuid
SpringApplicationJsonEnvironmentPostProcessor 解析以上表格中资源存在的SPRING_APPLICATION_JSONspring.application.json配置键配置的json字符串配置
ConfigDataEnvironmentPostProcessor 用于加载配置文件,如application.yml
ReactorEnvironmentPostProcessor spring.reactor.debug-agent.enabled=true时执行ReactorDebugAgent#init方法
IntegrationPropertiesEnvironmentPostProcessor 加载META-INF/spring.integration.properties中配置

SpringApplicationJsonEnvironmentPostProcessor加入的json类型的配置先于配置文件的加载,因此SPRING_APPLICATION_JSON
spring.application.json放在配置文件(如application.yml文件)中是不能生效的。

所有EnvironmentPostProcessor实现执行完后,新增的配置如下表,这些配置中除spring.application.json外,其它的优先级都在上面表格所述资源的后面。

配置类型名称 说明
*spring.application.json SPRING_APPLICATION_JSONspring.application.json为键配置的json格式数据。 对于servlet应用其优先级会排在servletConfigInitParams前面,其它情况会排在systemProperties前面
*random 提供以random.为前缀的随机值配置值获取
*[名称不固定,根据资源类型生成] 配置文件中提供的配置
*[Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'] application.yml配置文件中提供的配置
*[META-INF/spring.integration.properties] META-INF/spring.integration.properties中配置

对于application.yml及格式为application-${profile}.yml文件,一个文件一个配置类型名称,比如classpath下有如下配置文件

  • application.yml
  • application-app.yml
  • application-mid.yml
  • application-dao.yml

如果spring.profiles.active=dao,app,mid, 则生成的PropertySource名称和优先级顺序如下。
spring.profiles.active中配置越后面的profile优先级越高。不过默认配置文件application.yml最先加载。

配置类型名称
Config resource 'class path resource [application-mid.yml]' via location 'optional:classpath:/'
Config resource 'class path resource [application-app.yml]' via location 'optional:classpath:/'
Config resource 'class path resource [application-dao.yml]' via location 'optional:classpath:/'
Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'

注意:如果要提供动态配置且动态配置的优先级最高,则需要在本步骤中所有配置都加载完后再加载动态配置。

1.5.4 通过设置以spring.main为前缀的配置来设置SpringApplication对象的属性

bindToSpringApplication方法的源码如下:

java 复制代码
// spring boot SpringApplication源码
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    } catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

如果要通过配置来影响SpringApplication的属性,提供spring.main为前缀的配置即可. 如在application.yml

配置如下内容,则可以允许bean对象循环依赖

yaml 复制代码
spring:
  main:
    allowCircularReferences: true
    allowBeanDefinitionOverriding: true

上面示例中allowCircularReferencesallowBeanDefinitionOverriding都是SpringApplication对象的属性,可在源码中找到

1.5.5 如果是自定义环境类型,则转为应用类型相符的环境类型

SpringApplication启动时允许设置自定义类型的环境对象,不过在本步骤时会把这种环境对象转换为应用相对应的环境类型。

自定义的环境类型有两种情况进行设置

  1. 创建SpringApplication时通过setEnvironment()方法设置
  2. 创建SpringApplication时通过setApplicationContextFactory()
    方法设置一个创建自定义环境对象的ApplicationContextFactory实现类对象

1.6发布ApplicationEnvironmentPreparedEvent事件标识环境配置已准备好

见上一节[1.5.3 通知监听器环境对象以准备好](#1.5.3 通知监听器环境对象以准备好)

2. Spring容器的创建、准备和刷新

SpringApplication的象模板方法run代码可以看出,它对spring容器的操作分为3步

  1. 创建Spring容器对象ConfigurableApplicationContext,见后面2.1部分
  2. 对spring容器做刷新操作前的准备工作。这部分工作比较多,见后面2.2-2.6部分
  3. 刷新spring容器,见后面2.7部分

2.1创建Spring容器ConfigurableApplicationContext

SpringApplication对象使用ApplicationContextFactory抽象工厂创建spring容器,见下面源码。

另外,这个抽象工厂除了创建容器外,它还提供创建环境对象的工厂方法,见前面部分1.5.1环境对象创建

java 复制代码
// spring boot SpringApplication源码
protected ConfigurableApplicationContext createApplicationContext() {
    return this.applicationContextFactory.create(this.webApplicationType);
}

说明:applicationContextFactory可通过SpringApplication#setApplicationContextFactory方法指定。

其默认为DefaultApplicationContextFactory

2.1.1 使用DefaultApplicationContextFactory创建不同应用的spring容器

为何默认工厂类可以创建不同应用类型的spring容器?下面就看这个默认工厂类DefaultApplicationContextFactorycreate方法。

java 复制代码
// spring boot DefaultApplicationContextFactory源码
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
    try {
        // 第1个lambda表示使用META-INF/spring.factories文件加载的工厂创建容器,见下面代码[1]
        // 第2个lambda表示兜底方案,如果其它工厂没有为指定webApplicationType创建容器,则使用默认方法。见下面代码[2]
        return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
            this::createDefaultApplicationContext);
    } catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
            + "you may need a custom ApplicationContextFactory", ex);
    }
}

private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
    BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {

    // [1] 使用从META-INF/spring.factories文件加载的工厂创建容器,如果为空则继续
    for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
        getClass().getClassLoader())) {
        T result = action.apply(candidate, webApplicationType);
        if (result != null) {
            return result;
        }
    }
    return (defaultResult != null) ? defaultResult.get() : null;
}

// [2] 创建默认容器
private ConfigurableApplicationContext createDefaultApplicationContext() {
    if (!AotDetector.useGeneratedArtifacts()) {
        // 非AOT时使用
        return new AnnotationConfigApplicationContext();
    }
    return new GenericApplicationContext();
}

从上面代码可以知道

  1. DefaultApplicationContextFactory会先把创建容器的任务委托给从META-INF/spring.factories
    加载的ApplicationContextFactory实现类,这就回答了上面的问题。
  2. 即使同一应用类型在AOT时和非AOT时使用的容器不同。

2.1.2 spring boot中可以创建哪些类型的容器?

见下面表格,每个应用类型下ApplicationContextFactory可创建的容器:

WebApplicationType枚举 ApplicationContextFactory实现类 非AOT时创建容器 AOT时创建容器
SERVLET ReactiveWebServerApplicationContextFactory AnnotationConfigReactiveWebServerApplicationContext ReactiveWebServerApplicationContext
REACTIVE ServletWebServerApplicationContextFactory AnnotationConfigServletWebServerApplicationContext ServletWebServerApplicationContext
NONE DefaultApplicationContextFactory AnnotationConfigApplicationContext GenericApplicationContext

每个工厂类的源码比较简单,就不展示了。它们创建容器时都会执行的容器类的默认构造函数。

2.1.3 spring容器创建是会初始化哪些资源?

上面ApplicationContextFactory的3个实现都会执行容器的默认构造函数,这些默认构造函数及其父类构造函数都会做些初始操作,比如

  • AbstractApplicationContext需要指定ResourcePatternResolver对象,默认为PathMatchingResourcePatternResolver
    servlet应用为ServletContextResourcePatternResolver
  • GenericApplicationContext需要创建DefaultListableBeanFactorybean工厂对象
  • 上面3个AnnotationConfig容器会创建AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner
    创建这两个对象时,会间接触发spring容器创建Environment对象,环境对象默认为StandardEnvironment
    ,servlet应用为StandardServletEnvironment,reactive应用为StandardReactiveWebEnvironment.

注意目前容器创建的环境对象,与前面spring boot的SpringApplication创建的不一样。后面步骤spring boot会把自身的环境对象覆盖此处创建的环境对象,

同时也会更新scanner和reader持有的环境对象。为什么spring容器会重复创建环境对象呢?可以从spring

boot和spring容器属于两个不同的问题域进行分析,这里不做深入探讨。

2.1.3.1 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner使用环境对象干什么?

scanner和reader会用环境对象来创建ConditionEvaluator对象,它的作用是:scanner和reader在为bean创建BeanDefinition

时用它来判断bean是否满足创建条件。

ConditionEvaluator创建时会把环境对象、BeanFactory对象传递给ConditionContext对象。有些Condition实现类使用环境对象来获取配置做条件判断。

这就是环境对象在scanner和reader中的作用。

ConditionEvaluator本质是执行Condition对象的match方法判断是否需要创建bean。 Condition接口定义如下,其中context参数可以用来获取环境对象。

java 复制代码
// spring boot Condition源码
public interface Condition {

    /**
     * 检车条件是否满足
     * @param context 可以用来获取环境对象、BeanFactory对象
     * @param metadata 类的AnnotationMetadata或或者方法MethodMetadata
     * @return true条件满足
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

在执行match方法前,ConditionEvaluator从类或方法的Conditional注解获得Condition对象。在实际的spring

boot项目中,很少直接使用该注解,除非要定义自己的条件判断器,通常都是通过其它条件注解来获得。

spring boot中所有的ConditionalXxx条件注解都使用Conditional注解。 比如下面的ConditionalOnClass注解。

下面代码中OnClassCondition类实现了Condition接口,它是ConditionalOnClass注解的处理器,实现了类是否存在的判断逻辑。

java 复制代码
// spring boot ConditionalOnClass源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    Class<?>[] value() default {};

    String[] name() default {};
}
2.1.3.2 创建AnnotatedBeanDefinitionReader对象会初始化哪些配置?

AnnotatedBeanDefinitionReader用于为使用@Configuration注解的类创建BeanDefinition,执行它的构造函数时会完成如下配置。

  • 设置其beanNameGenerator值为AnnotationBeanNameGenerator, 它从注解的value属性获得bean名称,如果注解没有指定bean名称,则默认使用类名首字母小写作为bean名称。
  • 设置其scopeMetadataResolver值为AnnotationScopeMetadataResolver,它用于处理@Scope注解。
  • 执行AnnotationConfigUtils#registerAnnotationConfigProcessors静态方法往容器添加如下配置:
    • 设置beanFactory的dependencyComparator值为AnnotationAwareOrderComparator
    • 设置beanFactory的autowireCandidateResolver值为ContextAnnotationAutowireCandidateResolver
    • 添加用于处理@Configuration注解的ConfigurationClassPostProcessorbean工厂后处理器
    • 添加用于处理@Resource@PostConstruct@PreDestroy注解的AutowiredAnnotationBeanPostProcessorbean实例后处理器
    • 添加用于处理@Autowired@Value@Inject注解的CommonAnnotationBeanPostProcessorbean实例后处理器
    • 添加用于处理@PersistenceContext@PersistenceUnit注解的PersistenceAnnotationBeanPostProcessorbean实例后处理器
    • 添加用于处理@EventListener注解的EventListenerMethodProcessor
      bean工厂后处理器,该后处理器还实现了SmartInitializingSingleton接口,在容器刷新完成后创建监听器。
2.1.3.3 创建ClassPathBeanDefinitionScanner对象会初始化哪些配置?

ClassPathBeanDefinitionScanner用于扫描类上有spring@Component注解或Jakarta/java EE的@ManagedBean@Named注解。

它创建时会

  • 添加AnnotationTypeFilter过滤器用于匹配有@Component@ManagedBean@Named注解的类

  • 创建PathMatchingResourcePatternResolver类型的ResourcePatternResolver

  • 创建CachingMetadataReaderFactory类型MetadataReaderFactory

  • 加载META-INF/spring.components文件创建CandidateComponentsIndex对象,如果该文件不存在,则不创建。
    META-INF/spring.components也用于注册bean,是spring5.0增加的功能,spring6.1又废弃了,不建议使用。
    其文件格式为k=v的properties格式,格式为:类的全限定名=注解的全限定名或者类的全限定名=带有Indexed注解的类的全限定名
    ,比如下面配置示例

    properties 复制代码
    # 示例
    org.example.MyBeanController=org.springframework.stereotype.Component
2.1.3.4 spring容器实例化后还要做哪些准备?

spring为了扩展性,提供2套扩展方式,一套是基于事件的监听器、另一套是扩展接口。为了加载这些扩展类,又提供了各种灵活多样的加载方式:

注解、配置文件、系统配置、环境配置、命令行命令、META-INF/spring.factories、代码配置。

至此一个spring容器实例已经创建完成,如果把注解了@SpringBootApplication

类的BeanDefinition和前面创建的环境对象交给spring容器,就可以执行容器的刷新操作了。

但这样,在容器刷新之前,就没法对容器做个性化的控制,所以,还需要执行各种扩展操作。下面是SpringApplication对容器刷新前做的进一步准备工作。

java 复制代码
// spring boot SpringApplication 源码
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments, Banner printedBanner) {
    // 把spring boot创建的环境对象交给spring容器
    context.setEnvironment(environment);
    // 设置resourceLoader、bean名称生成器beanNameGenerator、类型转换服务ConversionService
    postProcessApplicationContext(context);
    // 
    addAotGeneratedInitializerIfNecessary(this.initializers);
    // 2.2执行ApplicationContextInitializer初始化Spring容器
    applyInitializers(context);
    // 2.3容器已准备好,事件通知,可发布ApplicationContextInitializedEvent事件标识容器已初始化
    listeners.contextPrepared(context);
    // 2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
        // 设置是否允许循环依赖
        autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
            // 设置是否允许覆盖bean定义
            listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    if (this.lazyInitialization) {
        // 延迟初始化
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    if (this.keepAlive) {
        // 会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束
        context.addApplicationListener(new KeepAlive());
    }
    // 添加bean工厂后处理器,保证默认配置优先级最低, 即名称defaultProperties的PropertySource
    context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
    if (!AotDetector.useGeneratedArtifacts()) {
        // 2.5加载主要资源primarySources和指定资源sources的BeanDefinition,
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
    }
    // 2.6发布ApplicationPreparedEvent事件
    listeners.contextLoaded(context);
}

上面代码中,简单的描述下,就不展示其详细源码,其它复杂的见后面章节2.2到2.6.

  1. context.setEnvironment(environment): 把spring boot创建的环境对象交给spring容器,这样保证了前面准备的环境对象才有用武之地。
  2. postProcessApplicationContext
    • 如果指定了ResourceLoader则添加到spring容器,此处默认为null,非必要不用指定,使用容器自带。
    • 如果制定了BeanNameGeneratorbean名称生成器beanNameGenerator,则添加到bean工厂。此处默认为null,非必要不用指定,使用容器自带。
    • 设置bean工厂的类型转换服务ConversionService,默认从环境对象获取,
      即前面提到的ApplicationConversionService
  3. addAotGeneratedInitializerIfNecessary,如果是运行的是native代码或者AOT编译运行,就会做下面2个事情
    • 把所有注册的AotApplicationContextInitializer
      容器初始化器添加到初始化器列表前面。AotApplicationContextInitializer实现了ApplicationContextInitializer接口。
    • AOT编译期间会生成一个以main方法所在类的名称为前缀ApplicationContextInitializer
      实现,这一步就会创建该实现类对象,并添加到其他AotApplicationContextInitializer初始化器的前面。
  4. 会把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用。bean名称为springApplicationArguments
  5. 通过allowCircularReferences属性设置是否允许循环依赖,通过allowBeanDefinitionOverriding属性设置是否允许覆盖重复的bean定义。
  6. 如果允许延迟加载,添加用于设置延迟初始化的bean工厂后处理器LazyInitializationBeanFactoryPostProcessor
    它会把在执行其后处理器方法前注册的BeanDefinition的lazyInit属性设置为true, 默认哪些BeanDefinition不会延迟加载:
    • 实现了SmartInitializingSingleton接口的类
    • BeanDefinition角色role==BeanDefinition.ROLE_INFRASTRUCTURE
  7. 如果允许保持存活,会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束。这个设置对servlet和reactive应用不需要。
  8. 为保证bean创建前对环境对象添加了新的配置后,默认配置的优先级永远最低,
    添加bean工厂后处理器PropertySourceOrderingBeanFactoryPostProcessor

2.2执行ApplicationContextInitializer初始化Spring容器

ApplicationContextInitializer扩展接口用于进一步设置spring容器所有可设置的属性,包括BeanFactory、ResourceLoader等。它在SpringApplication构造方法里加载,见前面[1.1.2 创建SpringApplication](#1.1.2 创建SpringApplication)

2.2.1 内置实现有哪些?

spring boot提供的内置实现和作用如下,

实现类 说明
ConfigurationWarningsApplicationContextInitializer 添加ConfigurationWarningsPostProcessor工厂后处理器用于检查@ComponentScan的包是否正确
DelegatingApplicationContextInitializer 代理context.initializer.classes配置的初始化器对象

2.2.2 实现ApplicationContextInitializer接口可以做些什么扩展?

这个时候配置相关的环境对象、spring容器都已有基本配置,就可以往容器中添加一些个性化功能,比如:

  1. 添加自定义的工厂后处理器BeanFactoryPostProcessor或者器扩展接口------BeanDefinitionRegistryPostProcessor
    ------可注册新的BeanDefinition的
  2. 加载自定义的配置文件、或者添加动态配置支持。
  3. 更改spring容器中各组件的默认实现,实现的个性化需求。

如果需要注册自定义的初始化器,则需要在META-INF/spring.factories文件中以properties格式添加即可,示例如下:

properties 复制代码
# 示例Application Context Initializers
org.springframework.context.ApplicationContextInitializer=com.example.context.MyApplicationContextInitializer

2.3 spring容器已准备好,事件通知

所有ApplicationContextInitializer容器初始化器都执行完,这个时候spring boot会做如下2件事情

  1. 通知所有SpringApplicationRunListener监听器的contextPrepared方法
  2. SpringApplicationRunListener的其中一个实现EventPublishingRunListener会发布ApplicationContextInitializedEvent
    事件通其他ApplicationListener表示容器已初始化

2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件

spring boot管理spring框架本身定义的context外,还管理自身的上下文,即BootstrapContext

,默认实现为DefaultBootstrapContext

这个BootstrapContext能够作用的范围到这一步也到此为止,因此会发布BootstrapContextClosedEvent

事件通知其监听器ApplicationListener<BootstrapContextClosedEvent>用于处理收尾工作。

这个监听器的通常通过BootstrapRegistryInitializer扩展添加,

该扩展在DefaultBootstrapContext创建后执行。示例如下:

java 复制代码
// 示例代码
public class MyBootstrapRegistryInitializer implements BootstrapRegistryInitializer {

    @Override
    public void initialize(BootstrapRegistry registry) {
        // spring context初始化时注册监听器:监听关闭事件
        registry.addCloseListener(event -> {
            // spring boot context已关闭
            BootstrapContext context = event.getBootstrapContext();
            // ....
        });
    }
}

2.5加载主要资源的BeanDefinition

2.5.1 哪些可以作为主要资源

spring boot中加载的初始资源如下代码:

java 复制代码
// spring boot SpringApplication 源码
public Set<Object> getAllSources() {
    Set<Object> allSources = new LinkedHashSet<>();
    // 2.5.1.1 primarySources类型为Class, 代表主要类
    if (!CollectionUtils.isEmpty(this.primarySources)) {
        allSources.addAll(this.primarySources);
    }
    // 2.5.1.2 sources类型为String, 可代表类、路径、包名
    if (!CollectionUtils.isEmpty(this.sources)) {
        allSources.addAll(this.sources);
    }
    return Collections.unmodifiableSet(allSources);
}

下面对上面的注释标注点做个说明

2.5.1.1 primarySources介绍

primarySources定义为private final Set<Class<?>> primarySources。通常为我们在类上注解了@SpringBootApplication

的类对象,如下面的例子,

java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        // MyApplication.class作为primarySources
        SpringApplication.run(MyApplication.class, args);
    }
}

另外,primarySources也可以是注解了其他spring注解的类的类对象。

2.5.1.2sources介绍

定义为private Set<String> sources,通常为

  1. 包全限定符名称
  2. XML文件在classpath下的相对路径
  3. 类名,指定的类应该是标有spring注解的类

指定source的示例代码如下

java 复制代码
// 示例代码
@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication();
        application.setSources(Set.of("my_spring.xml", "com.example.beans", "com.example.MyApplication"));
        application.run(args);
    }
}

2.5.2 加载资源的目的和作用

使用sources主要还是用于加载这些source代表的Bean定义,如下代码。

java 复制代码
// spring boot SpringApplication 源码
protected void load(ApplicationContext context, Object[] sources) {
    // ....
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    // .....
    loader.load();
}

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
    return new BeanDefinitionLoader(registry, sources);
}

BeanDefinitionLoader用于spring boot加载指定source表示的bean定义,如下源码。

java 复制代码
// spring boot BeanDefinitionLoader 源码
private void load(Object source) {
    Assert.notNull(source, "Source must not be null");
    if (source instanceof Class<?> clazz) {
        load(clazz);
        return;
    }
    if (source instanceof Resource resource) {
        load(resource);
        return;
    }
    if (source instanceof Package pack) {
        load(pack);
        return;
    }
    if (source instanceof CharSequence sequence) {
        load(sequence);
        return;
    }
    throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

当source为字符串时,它会转为相应的资源类型后在掉想要的load方法,如下源码

java 复制代码
// spring boot BeanDefinitionLoader 源码
private void load(CharSequence source) {
    // source可以使用${}表达式计算真正的值
    String resolvedSource = this.scanner.getEnvironment().resolvePlaceholders(source.toString());
    try {
        // 尝试当作类名来加载
        load(ClassUtils.forName(resolvedSource, null));
        return;
    } catch (IllegalArgumentException | ClassNotFoundException ex) {
        // swallow exception and continue
    }
    // 尝试当作资源文件来加载
    if (loadAsResources(resolvedSource)) {
        return;
    }
    // 尝试当作包名来加载
    Package packageResource = findPackage(resolvedSource);
    if (packageResource != null) {
        load(packageResource);
        return;
    }
    throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
}

下面以加载Class资源代表的Bean定义为例

java 复制代码
// spring boot BeanDefinitionLoader源码
private void load(Class<?> source) {
    ....
    // 使用AnnotatedBeanDefinitionReader来加载
    this.annotatedReader.register(source);
}

下面是AnnotatedBeanDefinitionReader为bean创建BeanDefinition的源码,省略了其他无关代码。

java 复制代码
// spring boot AnnotatedBeanDefinitionReader 源码
private <T> void doRegisterBean(Class<T> beanClass, String name, ....) {

    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    // 如果类不满足加载条件,则跳过。
    // 比如注解了@ConditionalOnBean("xxxBeanName"),xxxBeanName不存就会跳过,不会创建当前beanClass的BeanDefinition
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }

    abd.setAttribute(ConfigurationClassUtils.CANDIDATE_ATTRIBUTE, Boolean.TRUE);
    // ....
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

    // 解析 @Lazy @Primary @DependsOn @Role @Description
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    // ......

    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    // 为bean添加代理配置
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 把bean定义添加到BeanDefinitionRegistry,其实际就是BeanFactory的实现类
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

2.6 spring容器已加载完成

到这一步,容器就算可以执行刷新操作了,但为了扩展,在这种情况下仍然有可能还要对spring容器做进一步操作。spring

boot在这一步只提供了事件通知,如下所述2种监听器:

  1. 通知所有SpringApplicationRunListener监听器的contextLoaded方法
  2. SpringApplicationRunListener的其中一个实现EventPublishingRunListener会发布ApplicationPreparedEvent
    事件通其他ApplicationListener表示容器加载完成即将执行刷新操作。

2.7 刷新容器

现在终于可以刷新spring容器来创建bean来启动应用了。spring容器ApplicationContext

的refresh方法实现在抽象类AbstractApplicationContext,该实现采用模板方法模式,

定义了一套容器刷新流程。简单描述流程图如下:
刷新前准备工作 获取Bean工厂和配置bean工厂的基本特征 执行BeanFactoryPostProcessor 注册BeanPostProcessor 初始化MessageSource 初始化ApplicationEventMulticaster 注册ApplicationListener实现类 完成bean工厂初始化如初始化bean 刷新收尾如发布事件

对上面流程简要说明下:

  1. 刷新前准备工作: 比如验证环境对象中必要的配置是否存在
  2. 获取Bean工厂和配置bean工厂的基本特征
    • bean工厂通常由子类提供
    • 往bean工厂添加工厂后处理器、bean后处理器、BeanDefinition,这部分由默认实现也有让子类实现
  3. 执行BeanFactoryPostProcessor
    • 执行BeanDefinitionRegistry的postProcessBeanDefinitionRegistry方法往bean工厂添加BeanDefinition
    • 执行bean后处理器的postProcessBeanFactory方法
  4. 注册BeanPostProcessor: 主要是使用Bean工厂实例化BeanPostProcessor,并添加到bean工厂的beanPostProcessors列表中
  5. 完成bean工厂初始化如初始化bean:
    • 实例化所有单例bean,即scope为singleton的bean
    • 如果单例bean实现了SmartInitializingSingleton接口,则执行该bean的afterSingletonsInstantiated方法
  6. 刷新收尾,比如清理缓存对象,以及发布事件。与事件相关的菜哦在如下:
    • 先初始化LifecycleProcessor对象,默认为DefaultLifecycleProcessor,然后执行LifeCycle的start方法
    • 发布ContextRefreshedEvent事件,监听了该事件的ApplicationListener会被触发执行

3.容器刷新后处理

3.1发布ApplicationStartedEvent事件

到这一步,容器刷新完成,所有bean都已经创建,为了让应用感知到spring boot已经执行完spring容器的刷新操作,它提供了事件通知,如下所述:

  1. 通知所有SpringApplicationRunListener监听器的started方法,表示spring boot应用已启动完成。
  2. SpringApplicationRunListener的其中一个实现EventPublishingRunListener会发布ApplicationStartedEvent
    事件通其他ApplicationListener表示应用已启动完成

3.2执行ApplicationRunner和CommandLineRunner

实现ApplicationRunner和CommandLineRunner任意一个接口就可以获得命令行传参,并使用@Component注解等方式让spring管理。

下面是spring boot执行ApplicationRunner和CommandLineRunner接口的代码,这个没什么好解释的。

java 复制代码
// spring boot SpringApplication 源码
private void callRunners(ConfigurableApplicationContext context, ApplicationArguments args) {
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);
    Map<Runner, String> instancesToBeanNames = new IdentityHashMap<>();
    for (String beanName : beanNames) {
        instancesToBeanNames.put(beanFactory.getBean(beanName, Runner.class), beanName);
    }
    // 对bean进行排序:通过@Priority、@Order、PriorityOrdered接口、Order提供排序值
    Comparator<Object> comparator = getOrderComparator(beanFactory)
        .withSourceProvider(new FactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
    instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner) -> callRunner(runner, args));
}
private void callRunner(Runner runner, ApplicationArguments args) {
    if (runner instanceof ApplicationRunner) {
        callRunner(ApplicationRunner.class, runner, (applicationRunner) -> applicationRunner.run(args));
    }
    if (runner instanceof CommandLineRunner) {
        callRunner(CommandLineRunner.class, runner,
            (commandLineRunner) -> commandLineRunner.run(args.getSourceArgs()));
    }
}

3.3发布ApplicationReadyEvent事件

到这一步,spring boot的启动工作就算完成了,一切都已为应用准备好,为了让应用感知到spring boot已经启动完成,它提供了事件通知,如下所述:

  1. 通知所有SpringApplicationRunListener监听器的ready方法,表示spring boot应用启动就绪。
  2. SpringApplicationRunListener的其中一个实现EventPublishingRunListener会发布ApplicationReadyEvent
    事件通其他ApplicationListener表示应用已启动完成

3.4 异常处理

从创建环境对象开始到执行Runner接口,任何一个地方抛出异常,spring boot会通过事件通知给监听器,如下所述:

  1. 通知所有SpringApplicationRunListener监听器的failed方法,表示spring boot应用已启动失败.
  2. SpringApplicationRunListener的其中一个实现EventPublishingRunListener会发布ApplicationFailedEvent
    事件通其他ApplicationListener表示应用已启动失败

4 推荐阅读

spring启动过程发送事件总结可见之前博文《Spring及Springboot事件机制详解》

spring boot启动过程的扩展点和注册方式见《Spring boot接口扩展之SPI方式详解》

5 总结

  1. SpringApplication有几种应用方式?回看[1.1.1 创建SpringApplication](#1.1.1 创建SpringApplication)
  2. WebApplicationType对应的3种枚举类型对应的不同的ApplicationContextEnvironment实现类。回看[1.1.2.1 判断应用类型](#1.1.2.1 判断应用类型)
  3. SpringApplicationRunListener监听的各个事件触发时机
  4. 1.5创建ConfigurableEnvironment加载环境配置如环境变量和系统配置以及命令行参数是加载配置的重头戏,其中也反映了各个配置方式的优先级。
  5. 配置文件种以spring.main为前缀属性名为结尾的配置来设置SpringApplication对象的属性,回看[1.5.4 通过设置以spring.main为前缀的配置](#1.5.4 通过设置以spring.main为前缀的配置)
  6. 创建Spring ApplicationContext容器都会在构造器里面初始化一些资源,可回见[2.1.3 spring容器创建是会初始化哪些资源](#2.1.3 spring容器创建是会初始化哪些资源)
  7. 简单总结了下spring容器的刷新过程[2.7 刷新容器](#2.7 刷新容器)
相关推荐
陌上花开࿈2 小时前
调用第三方接口
java
Aileen_0v02 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
桂月二二4 小时前
Java与容器化:如何使用Docker和Kubernetes优化Java应用的部署
java·docker·kubernetes
liuxin334455664 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
小马爱打代码4 小时前
设计模式详解(建造者模式)
java·设计模式·建造者模式
栗子~~5 小时前
idea 8年使用整理
java·ide·intellij-idea
2301_801483695 小时前
Maven核心概念
java·maven
Q_19284999065 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
陈无左耳、6 小时前
Spring Boot应用开发实战:从入门到精通
spring boot
我要学编程(ಥ_ಥ)6 小时前
初始JavaEE篇 —— 网络原理---传输层协议:深入理解UDP/TCP
java·网络·tcp/ip·udp·java-ee