0


Spring boot启动过程详解

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

概要

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

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

每个部分又有如下图所示的步骤(图片比较宽,左右滑动查看):

#mermaid-svg-8vUJIbqX7O55K2fG {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .error-icon{fill:#552222;}#mermaid-svg-8vUJIbqX7O55K2fG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8vUJIbqX7O55K2fG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8vUJIbqX7O55K2fG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG .marker.cross{stroke:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8vUJIbqX7O55K2fG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster-label text{fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster-label span{color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .label text,#mermaid-svg-8vUJIbqX7O55K2fG span{fill:#333;color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .node rect,#mermaid-svg-8vUJIbqX7O55K2fG .node circle,#mermaid-svg-8vUJIbqX7O55K2fG .node ellipse,#mermaid-svg-8vUJIbqX7O55K2fG .node polygon,#mermaid-svg-8vUJIbqX7O55K2fG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8vUJIbqX7O55K2fG .node .label{text-align:center;}#mermaid-svg-8vUJIbqX7O55K2fG .node.clickable{cursor:pointer;}#mermaid-svg-8vUJIbqX7O55K2fG .arrowheadPath{fill:#333333;}#mermaid-svg-8vUJIbqX7O55K2fG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8vUJIbqX7O55K2fG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8vUJIbqX7O55K2fG .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-8vUJIbqX7O55K2fG .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster text{fill:#333;}#mermaid-svg-8vUJIbqX7O55K2fG .cluster span{color:#333;}#mermaid-svg-8vUJIbqX7O55K2fG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8vUJIbqX7O55K2fG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
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应用的启动过程,源码如下,注意阅读注释

// spring boot SpringApplication源码publicConfigurableApplicationContextrun(String... args){Startup startup =Startup.create();if(this.registerShutdownHook){// 默认为trueSpringApplication.shutdownHook.enableShutdownHookAddition();}// 1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializerDefaultBootstrapContext bootstrapContext =createBootstrapContext();ConfigurableApplicationContext context =null;configureHeadlessProperty();//1.3从spring.factories文件加载所有SpringApplicationRunListenersSpringApplicationRunListeners listeners =getRunListeners(args);// 1.4发布ApplicationStartingEvent事件标识正在启动应用
    listeners.starting(bootstrapContext,this.mainApplicationClass);try{// 命令行参数对象化ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);// 1.5和1.6 加载环境配置和发布ApplicationEnvironmentPreparedEvent事件ConfigurableEnvironment environment =prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 打印spring bannerBanner printedBanner =printBanner(environment);// 2.1创建Spring容器ConfigurableApplicationContext
        context =createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);// 2.2-2.6prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 2.7刷新容器refreshContext(context);// 刷新后处理器,钩子函数,空实现,留给子类实现afterRefresh(context, applicationArguments);
        startup.started();if(this.logStartupInfo){// 默认为true// 打印启动耗时newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);}// 3.1发布ApplicationStartedEvent事件
        listeners.started(context, startup.timeTakenToStarted());// 3.2执行ApplicationRunner和CommandLineRunnercallRunners(context, applicationArguments);}catch(Throwable ex){throwhandleRunFailure(context, ex, listeners);}try{if(context.isRunning()){// 3.3发布ApplicationReadyEvent事件
            listeners.ready(context, startup.ready());}}catch(Throwable ex){throwhandleRunFailure(context, ex,null);}return context;}

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

1. 环境配置准备

1.1 创建SpringApplication并执行其run方法

1.1.1 创建SpringApplication

SpringApplication

类大家都不陌生,它是spring boot提供的外观类,屏蔽了spring庞杂的内部结构,通过它只需要一行代码即可启动一个spring
boot应用。它在启动时有3中创建方式:

  1. 执行其静态方法run方法,这是我们经常使用的方式。
// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){SpringApplication.run(MyApplication.class, args);// 等同于// new SpringApplication(MyApplication.class).run(args);}}
  1. 通过new关键字直接创建
// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){SpringApplication application =newSpringApplication(MyApplication.class);
        application.setAllowCircularReferences(true);
        application.setAllowBeanDefinitionOverriding(true);
        application.setLogStartupInfo(true);
        application.setWebApplicationType(WebApplicationType.SERVLET);
        application.run(args);}}
  1. 使用SpringApplicationBuilder
// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){newSpringApplicationBuilder(MyApplication.class).allowCircularReferences(true).logStartupInfo(true).web(WebApplicationType.REACTIVE).run();}}

3种方式简要说明:

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

1.1.2 创建SpringApplication

下面是

SpringApplication

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

// spring boot SpringApplication源码publicSpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources){// 用于加载资源,默认为nullthis.resourceLoader = resourceLoader;Assert.notNull(primarySources,"PrimarySources must not be null");// 即被@SpringBootApplication注解的类,以它为根进行扫描所有bean定义this.primarySources =newLinkedHashSet<>(Arrays.asList(primarySources));// 1.1.2.1 判断应用类型,分别是SERVLET|REACTIVE|NONE。后面会根据应用类型创建Spring容器this.webApplicationType =WebApplicationType.deduceFromClasspath();// 1.1.2.2 加载spring.factories中配置的扩展实现类this.bootstrapRegistryInitializers =newArrayList<>(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。

// 示例代码@GeneratedpublicclassMyApplication__BeanDefinitions{/**
     * Get the bean definition for 'MyApplication'.
     */publicstaticBeanDefinitiongetMyApplicationBeanDefinition(){RootBeanDefinition beanDefinition =newRootBeanDefinition(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. ApplicationListener : 这里注册的监听器可以监听spring boot应用的整个过程的事件
1.1.2.3 其它
  1. resourceLoader默认为null,
  2. primarySources为spring加载业务代码bean的起点,通常为被@SpringBootApplication注解的类,也可以是其它spring注解的类。
  3. mainApplicationClass为main方法所在类

1.2 创建DefaultBootstrapContext并执行BootstrapRegistryInitializer

SpringApplication

源码如下:

// spring boot SpringApplication源码privateDefaultBootstrapContextcreateBootstrapContext(){// 1.2.1 创建启动上下文DefaultBootstrapContext bootstrapContext =newDefaultBootstrapContext();// 1.2.2 执行BootstrapRegistryInitializerthis.bootstrapRegistryInitializers.forEach((initializer)-> initializer.initialize(bootstrapContext));return bootstrapContext;}

1.2.1 创建启动上下文

DefaultBootstrapContext

实现了

BootstrapRegistry

BootstrapContext

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

1.2.2 执行BootstrapRegistryInitializer

BootstrapRegistryInitializer

通过

spring.factories

文件注册,一般用于往

BootstrapRegistry

DefaultBootstrapContext

注册自定义框架的对象,用在后面某些步骤中发挥作用。

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

SpringApplicationRunListener

用于监听

SpringApplication

启动过程的特定步骤,它定义的方法与启动步骤一一对应。

SpringApplication

中对应的源码如下,注意阅读注释,后面根据注释的编号做进一步分析。

// spring boot SpringApplication源码privateSpringApplicationRunListenersgetRunListeners(String[] args){// 1.3.1 从spring.factories加载SpringApplicationRunListenerArgumentResolver 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 =newArrayList<>(listeners);
        listeners.add(hookListener);}// 1.3.3 创建一个外观对象来编排各个监听执行器的执行returnnewSpringApplicationRunListeners(logger, listeners,this.applicationStartup);}

1.3.1 从spring.factories加载SpringApplicationRunListener

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

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

**关于

SpringApplicationRunListener

需说明如下:**

SpringApplicationRunListener

ApplicationListener

一样都属于监听器(观察者),不过它用于观察spring boot的启动过程,

SpringApplication

在各个阶段通过其管理器

SpringApplicationRunListeners

来执行各个

SpringApplicationRunListener

对象的

阶段函数

(如

starting

started

ready

)。

1.3.2 从ThreadLocal中获取应用钩子

springboot3.0提供了启动应用程序可以设置钩子,由这个钩子提供一个

SpringApplicationRunListener

对象,比如下面代码

// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){SpringApplication.withHook(newMySpringApplicationHook(),()->{SpringApplication.run(MyApplication.class, args);});}}publicclassMySpringApplicationHookimplementsSpringApplicationHook{@OverridepublicSpringApplicationRunListenergetRunListener(SpringApplication springApplication){returnnewMySpringApplicationRunListener(springApplication);}}

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

  1. AOT编译使用该钩子在spring容器刷新前抛出异常来终止动态类扫描,从而做到不启动应用。因此使用AOT编译的程序,不可在spring容器刷新前使用非守护 线程做长时间处理,否则影响编译时间。
  2. SpringApplication应用监听器隔离

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

SpringApplicationRunListeners

是个外观类,它提供了与

SpringApplicationRunListener

一致的方法,在发布事件时会做两件事:

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

见下面

SpringApplicationRunListeners

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

// spring boot源码voidstarting(ConfigurableBootstrapContext bootstrapContext,Class<?> mainApplicationClass){doWithListeners("spring.boot.application.starting",(listener)-> listener.starting(bootstrapContext),(step)->{if(mainApplicationClass !=null){
                step.tag("mainApplicationClass", mainApplicationClass.getName());}});}privatevoiddoWithListeners(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

,
下面是其源码

// spring boot EventPublishingRunListener源码EventPublishingRunListener(SpringApplication application,String[] args){this.application = application;this.args = args;this.initialMulticaster =newSimpleApplicationEventMulticaster();}publicvoidstarting(ConfigurableBootstrapContext bootstrapContext){multicastInitialEvent(newApplicationStartingEvent(bootstrapContext,this.application,this.args));}privatevoidmulticastInitialEvent(ApplicationEvent event){// 1.4.1 每次发布事件都更新监听器列表,保障每步中新加监听器能收到事件refreshApplicationListeners();// 1.4.2 使用多播器分发事件this.initialMulticaster.multicastEvent(event);}privatevoidrefreshApplicationListeners(){this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);}

1.4.1 更新

ApplicationListener

监听器列表

在spring boot的启动各阶段,都有可能往

SpringApplication

添加监听器,为了保障新加监听器能收到事件,每次都要刷新多播器中的监听器列表。
如下面的示例代码,如果应用类型为

REACTIVE

则添加一个监听器:

// 示例代码publicclassMySpringApplicationRunListenerimplementsSpringApplicationRunListener{privatefinalSpringApplication application;publicMySpringApplicationRunListener(SpringApplication application){this.application = application;}@Overridepublicvoidstarting(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加载环境配置如环境变量和系统配置以及命令行参数

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

// spring boot SpringApplication源码privateConfigurableEnvironmentprepareEnvironment(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 =newEnvironmentConverter(getClassLoader());// deduceEnvironmentClass方法根据应用类型返回对应的环境类型// 把environment转换为应用对应的环境类型对象 
        environment = environmentConverter.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 把所有source使用ConfigurationPropertySourcesPropertySource来管理,ConfigurationPropertySources.attach(environment);return environment;}

1.5.1 创建环境对象

// spring boot SpringApplication源码privateConfigurableEnvironmentgetOrCreateEnvironment(){if(this.environment !=null){returnthis.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 :newApplicationEnvironment();}

上面代码是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应用,加载的环境配置类型有:(下面标星*
的是所有应用类型都有)
配置类型名称说明servletConfigInitParamsservlet应用配置servletContextInitParamsservlet容器上下文配置jndiPropertiesJNDI环境配置systemProperties系统属性配置systemEnvironment系统环境变量配置

1.5.2 配置环境对象

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

// spring boot SpringApplication源码protectedvoidconfigureEnvironment(ConfigurableEnvironment environment,String[] args){if(this.addConversionService){// 默认位处// 1. 添加类型解析器,spring内置了很多类型之间的转换和类型的格式化器
        environment.setConversionService(newApplicationConversionService());}// 2. 装载默认配置和命令行指定配置configurePropertySources(environment, args);// 配置profile,(实现为空,留给SpringApplication子类扩展的)configureProfiles(environment, args);}
  1. 默认添加ApplicationConversionService对象作为类型解析器,这个解析器内置了很多类型转换器和格式化器,具体可见该类的源码,比较简单。在开发中也可以用它来做对象转换。
  2. 装载默认配置和命令行指定配置,见下面代码 1. 如果设置了默认配置,则添加名为defaultPropertiesPropertySource,并添加source列表尾部。2. 对于命令行配置,如--server.port=8080, 则添加一个名为commandLineArgsPropertySource,并添加source列表头部。// spring boot SpringApplication源码protectedvoidconfigurePropertySources(ConfigurableEnvironment environment,String[] args){MutablePropertySources sources = environment.getPropertySources();// i 添加defaultProperties sourceif(!CollectionUtils.isEmpty(this.defaultProperties)){DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);}// ii 添加commandLineArgs sourceif(this.addCommandLineProperties && args.length >0){String name ="commandLineArgs";if(sources.contains(name)){PropertySource<?> source = sources.get(name);// 组合模式,组合多个命令行配置CompositePropertySource composite =newCompositePropertySource(name); composite .addPropertySource(newSimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite);}else{ sources.addFirst(newSimpleCommandLinePropertySource(args));}}}

SimpleCommandLinePropertySource {name=‘commandLineArgs’}

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

到这一步了,环境对象该准备的基本配置都准备好了,对于Servlet应用,加载的环境配置类型有:(下面标星的是所有应用类型都有)
配置类型名称说明
configurationPropertiesattach下面所有配置,通过它可以对所有配置进行很方便的迭代*commandLineArgs命令行配置,如

java -jar app.jar --server.port=8080

servletConfigInitParamsservlet应用配置servletContextInitParamsservlet容器上下文配置jndiPropertiesJNDI环境配置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_JSON
    
    spring.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_JSON

spring.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

方法的源码如下:

// spring boot SpringApplication源码protectedvoidbindToSpringApplication(ConfigurableEnvironment environment){try{Binder.get(environment).bind("spring.main",Bindable.ofInstance(this));}catch(Exception ex){thrownewIllegalStateException("Cannot bind to SpringApplication", ex);}}

如果要通过配置来影响

SpringApplication

的属性,提供

spring.main

为前缀的配置即可. 如在

application.yml

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

spring:main:allowCircularReferences:trueallowBeanDefinitionOverriding:true

上面示例中

allowCircularReferences

allowBeanDefinitionOverriding

都是

SpringApplication

对象的属性,可在源码中找到

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

SpringApplication

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

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

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

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

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

SpringApplication

的象模板方法

run

代码可以看出,它对spring容器的操作分为3步

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

2.1创建Spring容器ConfigurableApplicationContext

// spring boot SpringApplication源码protectedConfigurableApplicationContextcreateApplicationContext(){returnthis.applicationContextFactory.create(this.webApplicationType);}

说明:

applicationContextFactory

可通过

SpringApplication#setApplicationContextFactory

方法指定。
其默认为

DefaultApplicationContextFactory

2.1.1 使用

DefaultApplicationContextFactory

创建不同应用的spring容器

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

DefaultApplicationContextFactory

create

方法。

// spring boot DefaultApplicationContextFactory源码@OverridepublicConfigurableApplicationContextcreate(WebApplicationType webApplicationType){try{// 第1个lambda表示使用META-INF/spring.factories文件加载的工厂创建容器,见下面代码[1]// 第2个lambda表示兜底方案,如果其它工厂没有为指定webApplicationType创建容器,则使用默认方法。见下面代码[2]returngetFromSpringFactories(webApplicationType,ApplicationContextFactory::create,this::createDefaultApplicationContext);}catch(Exception ex){thrownewIllegalStateException("Unable create a default ApplicationContext instance, "+"you may need a custom ApplicationContextFactory", ex);}}private<T>TgetFromSpringFactories(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] 创建默认容器privateConfigurableApplicationContextcreateDefaultApplicationContext(){if(!AotDetector.useGeneratedArtifacts()){// 非AOT时使用returnnewAnnotationConfigApplicationContext();}returnnewGenericApplicationContext();}

从上面代码可以知道

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

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

见下面表格,每个应用类型下

ApplicationContextFactory

可创建的容器:
WebApplicationType枚举ApplicationContextFactory实现类非AOT时创建容器AOT时创建容器SERVLETReactiveWebServerApplicationContextFactoryAnnotationConfigReactiveWebServerApplicationContextReactiveWebServerApplicationContextREACTIVEServletWebServerApplicationContextFactoryAnnotationConfigServletWebServerApplicationContextServletWebServerApplicationContextNONEDefaultApplicationContextFactoryAnnotationConfigApplicationContextGenericApplicationContext
每个工厂类的源码比较简单,就不展示了。它们创建容器时都会执行的容器类的默认构造函数。

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
AnnotatedBeanDefinitionReader

ClassPathBeanDefinitionScanner

使用环境对象干什么?

scanner和reader会用环境对象来创建

ConditionEvaluator

对象,它的作用是:scanner和reader在为bean创建

BeanDefinition

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

ConditionEvaluator

创建时会把环境对象、BeanFactory对象传递给

ConditionContext

对象。有些

Condition

实现类使用环境对象来获取配置做条件判断。
这就是环境对象在scanner和reader中的作用。

ConditionEvaluator

本质是执行

Condition

对象的

match

方法判断是否需要创建bean。

Condition

接口定义如下,其中context参数可以用来获取环境对象。

// spring boot Condition源码publicinterfaceCondition{/**
     * 检车条件是否满足
     * @param context 可以用来获取环境对象、BeanFactory对象
     * @param metadata 类的AnnotationMetadata或或者方法MethodMetadata
     * @return true条件满足
     */booleanmatches(ConditionContext context,AnnotatedTypeMetadata metadata);}

在执行

match

方法前,

ConditionEvaluator

从类或方法的

Conditional

注解获得

Condition

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

Conditional

注解。 比如下面的

ConditionalOnClass

注解。
下面代码中

OnClassCondition

类实现了

Condition

接口,它是

ConditionalOnClass

注解的处理器,实现了类是否存在的判断逻辑。

// spring boot ConditionalOnClass源码@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnClassCondition.class)public@interfaceConditionalOnClass{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注解的类的全限定名 ,比如下面配置示例# 示例org.example.MyBeanController=org.springframework.stereotype.Component
2.1.3.4 spring容器实例化后还要做哪些准备?

spring为了扩展性,提供2套扩展方式,一套是基于事件的监听器、另一套是扩展接口。为了加载这些扩展类,又提供了各种灵活多样的加载方式:
注解、配置文件、系统配置、环境配置、命令行命令、

META-INF/spring.factories

、代码配置。

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

@SpringBootApplication

类的BeanDefinition和前面创建的环境对象交给spring容器,就可以执行容器的刷新操作了。
但这样,在容器刷新之前,就没法对容器做个性化的控制,所以,还需要执行各种扩展操作。下面是

SpringApplication

对容器刷新前做的进一步准备工作。

// spring boot SpringApplication 源码privatevoidprepareContext(DefaultBootstrapContext bootstrapContext,ConfigurableApplicationContext context,ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments,Banner printedBanner){// 把spring boot创建的环境对象交给spring容器
    context.setEnvironment(environment);// 设置resourceLoader、bean名称生成器beanNameGenerator、类型转换服务ConversionServicepostProcessApplicationContext(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 beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if(printedBanner !=null){
        beanFactory.registerSingleton("springBootBanner", printedBanner);}if(beanFactory instanceofAbstractAutowireCapableBeanFactory autowireCapableBeanFactory){// 设置是否允许循环依赖
        autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);if(beanFactory instanceofDefaultListableBeanFactory listableBeanFactory){// 设置是否允许覆盖bean定义
            listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}if(this.lazyInitialization){// 延迟初始化
        context.addBeanFactoryPostProcessor(newLazyInitializationBeanFactoryPostProcessor());}if(this.keepAlive){// 会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束
        context.addApplicationListener(newKeepAlive());}// 添加bean工厂后处理器,保证默认配置优先级最低, 即名称defaultProperties的PropertySource
    context.addBeanFactoryPostProcessor(newPropertySourceOrderingBeanFactoryPostProcessor(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(newObject[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,非必要不用指定,使用容器自带。
  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容器

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格式添加即可,示例如下:

# 示例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

创建后执行。示例如下:

// 示例代码publicclassMyBootstrapRegistryInitializerimplementsBootstrapRegistryInitializer{@Overridepublicvoidinitialize(BootstrapRegistry registry){// spring context初始化时注册监听器:监听关闭事件
        registry.addCloseListener(event ->{// spring boot context已关闭BootstrapContext context = event.getBootstrapContext();// ....});}}

2.5加载主要资源的BeanDefinition

2.5.1 哪些可以作为主要资源

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

// spring boot SpringApplication 源码publicSet<Object>getAllSources(){Set<Object> allSources =newLinkedHashSet<>();// 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);}returnCollections.unmodifiableSet(allSources);}

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

2.5.1.1
primarySources

介绍

primarySources

定义为

private final Set<Class<?>> primarySources

。通常为我们在类上注解了

@SpringBootApplication

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

// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){// MyApplication.class作为primarySourcesSpringApplication.run(MyApplication.class, args);}}

另外,

primarySources

也可以是注解了其他spring注解的类的类对象。

2.5.1.2
sources

介绍

定义为

private Set<String> sources

,通常为

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

指定source的示例代码如下

// 示例代码@SpringBootApplicationpublicclassMyApplication{publicstaticvoidmain(String[] args){SpringApplication application =newSpringApplication();
        application.setSources(Set.of("my_spring.xml","com.example.beans","com.example.MyApplication"));
        application.run(args);}}

2.5.2 加载资源的目的和作用

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

// spring boot SpringApplication 源码protectedvoidload(ApplicationContext context,Object[] sources){// ....BeanDefinitionLoader loader =createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);// .....
    loader.load();}protectedBeanDefinitionLoadercreateBeanDefinitionLoader(BeanDefinitionRegistry registry,Object[] sources){returnnewBeanDefinitionLoader(registry, sources);}
BeanDefinitionLoader

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

// spring boot BeanDefinitionLoader 源码privatevoidload(Object source){Assert.notNull(source,"Source must not be null");if(source instanceofClass<?> clazz){load(clazz);return;}if(source instanceofResource resource){load(resource);return;}if(source instanceofPackage pack){load(pack);return;}if(source instanceofCharSequence sequence){load(sequence);return;}thrownewIllegalArgumentException("Invalid source type "+ source.getClass());}

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

// spring boot BeanDefinitionLoader 源码privatevoidload(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;}thrownewIllegalArgumentException("Invalid source '"+ resolvedSource +"'");}

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

// spring boot BeanDefinitionLoader源码privatevoidload(Class<?> source){....// 使用AnnotatedBeanDefinitionReader来加载this.annotatedReader.register(source);}

下面是

AnnotatedBeanDefinitionReader

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

// spring boot AnnotatedBeanDefinitionReader 源码private<T>voiddoRegisterBean(Class<T> beanClass,String name,....){AnnotatedGenericBeanDefinition abd =newAnnotatedGenericBeanDefinition(beanClass);// 如果类不满足加载条件,则跳过。// 比如注解了@ConditionalOnBean("xxxBeanName"),xxxBeanName不存就会跳过,不会创建当前beanClass的BeanDefinitionif(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 @DescriptionAnnotationConfigUtils.processCommonDefinitionAnnotations(abd);// ......BeanDefinitionHolder definitionHolder =newBeanDefinitionHolder(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

,该实现采用模板方法模式,
定义了一套容器刷新流程。简单描述流程图如下:

#mermaid-svg-01cfNoJcdCkKABjY {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .error-icon{fill:#552222;}#mermaid-svg-01cfNoJcdCkKABjY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-01cfNoJcdCkKABjY .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-01cfNoJcdCkKABjY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-01cfNoJcdCkKABjY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-01cfNoJcdCkKABjY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-01cfNoJcdCkKABjY .marker.cross{stroke:#333333;}#mermaid-svg-01cfNoJcdCkKABjY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-01cfNoJcdCkKABjY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster-label text{fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster-label span{color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .label text,#mermaid-svg-01cfNoJcdCkKABjY span{fill:#333;color:#333;}#mermaid-svg-01cfNoJcdCkKABjY .node rect,#mermaid-svg-01cfNoJcdCkKABjY .node circle,#mermaid-svg-01cfNoJcdCkKABjY .node ellipse,#mermaid-svg-01cfNoJcdCkKABjY .node polygon,#mermaid-svg-01cfNoJcdCkKABjY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-01cfNoJcdCkKABjY .node .label{text-align:center;}#mermaid-svg-01cfNoJcdCkKABjY .node.clickable{cursor:pointer;}#mermaid-svg-01cfNoJcdCkKABjY .arrowheadPath{fill:#333333;}#mermaid-svg-01cfNoJcdCkKABjY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-01cfNoJcdCkKABjY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-01cfNoJcdCkKABjY .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-01cfNoJcdCkKABjY .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-01cfNoJcdCkKABjY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-01cfNoJcdCkKABjY .cluster text{fill:#333;}#mermaid-svg-01cfNoJcdCkKABjY .cluster span{color:#333;}#mermaid-svg-01cfNoJcdCkKABjY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-01cfNoJcdCkKABjY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
刷新前准备工作

获取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接口的代码,这个没什么好解释的。

// spring boot SpringApplication 源码privatevoidcallRunners(ConfigurableApplicationContext context,ApplicationArguments args){ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();String[] beanNames = beanFactory.getBeanNamesForType(Runner.class);Map<Runner,String> instancesToBeanNames =newIdentityHashMap<>();for(String beanName : beanNames){
        instancesToBeanNames.put(beanFactory.getBean(beanName,Runner.class), beanName);}// 对bean进行排序:通过@Priority、@Order、PriorityOrdered接口、Order提供排序值Comparator<Object> comparator =getOrderComparator(beanFactory).withSourceProvider(newFactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
    instancesToBeanNames.keySet().stream().sorted(comparator).forEach((runner)->callRunner(runner, args));}privatevoidcallRunner(Runner runner,ApplicationArguments args){if(runner instanceofApplicationRunner){callRunner(ApplicationRunner.class, runner,(applicationRunner)-> applicationRunner.run(args));}if(runner instanceofCommandLineRunner){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. SpringApplicationRunListener监听的各个事件触发时机
标签: spring boot java spring

本文转载自: https://blog.csdn.net/chyohn/article/details/141925255
版权归原作者 chyohn 所有, 如有侵权,请联系我们删除。

“Spring boot启动过程详解”的评论:

还没有评论