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事件标识环境配置已准备好

以上步骤过程主要在

  1. SpringApplication

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

  1. // 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事件标识正在启动应用
  2. 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
  3. context =createApplicationContext();
  4. context.setApplicationStartup(this.applicationStartup);// 2.2-2.6prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 2.7刷新容器refreshContext(context);// 刷新后处理器,钩子函数,空实现,留给子类实现afterRefresh(context, applicationArguments);
  5. startup.started();if(this.logStartupInfo){// 默认为true// 打印启动耗时newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);}// 3.1发布ApplicationStartedEvent事件
  6. 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事件
  7. listeners.ready(context, startup.ready());}}catch(Throwable ex){throwhandleRunFailure(context, ex,null);}return context;}

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

1. 环境配置准备

1.1 创建SpringApplication并执行其run方法

1.1.1 创建SpringApplication

  1. SpringApplication

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

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

3种方式简要说明:

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

1.1.2 创建SpringApplication

下面是

  1. SpringApplication

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

  1. // 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 判断应用类型

应用类型

  1. WebApplicationType

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

  1. ApplicationContextFactory


工厂方法创建spring容器对象。下面表格是抽象工厂各实现提供的容器类和环境对象类。
枚举Factory实现类AOT编译时的容器类非AOT时的容器类提供的Environment实现类SERVLET

  1. ServletWebServerApplicationContextFactory
  1. ServletWebServerApplicationContext
  1. AnnotationConfigServletWebServerApplicationContext
  1. ApplicationServletEnvironment

REACTIVE

  1. ReactiveWebServerApplicationContextFactory
  1. ReactiveWebServerApplicationContext
  1. AnnotationConfigReactiveWebServerApplicationContext
  1. ApplicationReactiveWebEnvironment

NONE

  1. DefaultApplicationContextFactory
  1. GenericApplicationContext
  1. AnnotationConfigApplicationContext
  1. ApplicationEnvironment

spring boot从3.0开始支持GraalVM Native编译即AOT编译,AOT编译要求所有代码都是可达、静态的,通过注解在运行时动态加载具体类型违背AOT的原则,
Spring boot为支持AOT编译需要提前为通过注解配置的bean生成代码和AOP时的字节码,比如下面为示例的启动类

  1. MyApplication

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

  1. // 示例代码@GeneratedpublicclassMyApplication__BeanDefinitions{/**
  2. * Get the bean definition for 'MyApplication'.
  3. */publicstaticBeanDefinitiongetMyApplicationBeanDefinition(){RootBeanDefinition beanDefinition =newRootBeanDefinition(MyApplication.class);
  4. beanDefinition.setTargetType(MyApplication.class);ConfigurationClassUtils.initializeConfigurationClass(MyApplication.class);
  5. 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

  1. SpringApplication

源码如下:

  1. // 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 创建启动上下文

  1. DefaultBootstrapContext

实现了

  1. BootstrapRegistry

  1. BootstrapContext

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

1.2.2 执行BootstrapRegistryInitializer

  1. BootstrapRegistryInitializer

通过

  1. spring.factories

文件注册,一般用于往

  1. BootstrapRegistry

  1. DefaultBootstrapContext

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

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

  1. SpringApplicationRunListener

用于监听

  1. SpringApplication

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

  1. SpringApplication

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

  1. // spring boot SpringApplication源码privateSpringApplicationRunListenersgetRunListeners(String[] args){// 1.3.1 从spring.factories加载SpringApplicationRunListenerArgumentResolver argumentResolver =ArgumentResolver.of(SpringApplication.class,this);
  2. argumentResolver = argumentResolver.and(String[].class, args);List<SpringApplicationRunListener> listeners =getSpringFactoriesInstances(SpringApplicationRunListener.class,
  3. argumentResolver);// 1.3.2 从ThreadLocal中获取应用钩子SpringApplicationHook hook = applicationHook.get();SpringApplicationRunListener hookListener =(hook !=null)? hook.getRunListener(this):null;if(hookListener !=null){// 把钩子提供的监听器执行器加入listeners
  4. listeners =newArrayList<>(listeners);
  5. 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,如下面代码
  1. // 示例代码publicclassMySpringApplicationRunListenerimplementsSpringApplicationRunListener{privatefinalSpringApplication application;publicMySpringApplicationRunListener(SpringApplication application){this.application = application;}// 其它代码...}

**关于

  1. SpringApplicationRunListener

需说明如下:**

  1. SpringApplicationRunListener

  1. ApplicationListener

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

  1. SpringApplication

在各个阶段通过其管理器

  1. SpringApplicationRunListeners

来执行各个

  1. SpringApplicationRunListener

对象的

  1. 阶段函数

(如

  1. starting

  1. started

  1. ready

)。

1.3.2 从ThreadLocal中获取应用钩子

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

  1. SpringApplicationRunListener

对象,比如下面代码

  1. // 示例代码@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 创建一个外观对象来编排各个监听执行器的执行

  1. SpringApplicationRunListeners

是个外观类,它提供了与

  1. SpringApplicationRunListener

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

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

见下面

  1. SpringApplicationRunListeners

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

  1. // spring boot源码voidstarting(ConfigurableBootstrapContext bootstrapContext,Class<?> mainApplicationClass){doWithListeners("spring.boot.application.starting",(listener)-> listener.starting(bootstrapContext),(step)->{if(mainApplicationClass !=null){
  2. 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){
  3. stepAction.accept(step);}
  4. step.end();}

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

接上面

  1. SpringApplicationRunListeners

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

  1. EventPublishingRunListener

,
方法调用顺序:

  1. SpringApplicationRunListeners#starting

-->

  1. EventPublishingRunListener#starting

,
下面是其源码

  1. // 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 更新

  1. ApplicationListener

监听器列表

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

  1. SpringApplication

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

  1. REACTIVE

则添加一个监听器:

  1. // 示例代码publicclassMySpringApplicationRunListenerimplementsSpringApplicationRunListener{privatefinalSpringApplication application;publicMySpringApplicationRunListener(SpringApplication application){this.application = application;}@Overridepublicvoidstarting(ConfigurableBootstrapContext bootstrapContext){if(application.getWebApplicationType().equals(WebApplicationType.REACTIVE)){
  2. application.addListeners(event ->{System.out.println("REACTIVE APP trigger event: "+ event);});}}}

1.4.2 使用多播器分发事件

这里使用的多播器和Spring容器默认使用的多播器属于同一个类即

  1. SimpleApplicationEventMulticaster

,该类在实现上不单是维护一个监听器列表。
在Spring事件机制的实现中,

  1. ApplicationEventMulticaster

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

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

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

  1. // 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 通知监听器环境对象以准备好
  2. 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转换为应用对应的环境类型对象
  3. environment = environmentConverter.convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 把所有source使用ConfigurationPropertySourcesPropertySource来管理,ConfigurationPropertySources.attach(environment);return environment;}

1.5.1 创建环境对象

  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){
  2. environment =ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);}return(environment !=null)? environment :newApplicationEnvironment();}

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

  1. ApplicationContextFactory

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

1. 确定环境对象类型

在该步骤中,spring boot根据应用类型

  1. WebApplicationType

选择可以支持的

  1. ApplicationContextFactory

抽象工厂的实现类,
通过该抽象工厂实现类来创建

  1. ConfigurableEnvironment

对象。下表是

  1. WebApplicationType

对应的环境对象
WebApplicationType枚举ApplicationContextFactory实现类名提供的Environment实现类SERVLET

  1. ReactiveWebServerApplicationContextFactory
  1. org.springframework.boot.web.servlet.context.ApplicationServletEnvironment

REACTIVE

  1. ServletWebServerApplicationContextFactory
  1. org.springframework.boot.web.reactive.context.ApplicationReactiveWebEnvironment

NONE

  1. DefaultApplicationContextFactory
  1. org.springframework.boot.ApplicationEnvironment

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

这个时候会把各种类型的配置装载为

  1. PropertySource

对象,对于Servlet应用,加载的环境配置类型有:(下面标星*
的是所有应用类型都有)
配置类型名称说明servletConfigInitParamsservlet应用配置servletContextInitParamsservlet容器上下文配置jndiPropertiesJNDI环境配置systemProperties系统属性配置systemEnvironment系统环境变量配置

1.5.2 配置环境对象

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

  1. // spring boot SpringApplication源码protectedvoidconfigureEnvironment(ConfigurableEnvironment environment,String[] args){if(this.addConversionService){// 默认位处// 1. 添加类型解析器,spring内置了很多类型之间的转换和类型的格式化器
  2. 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命令行配置,如

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

servletConfigInitParamsservlet应用配置servletContextInitParamsservlet容器上下文配置jndiPropertiesJNDI环境配置systemProperties系统属性配置systemEnvironment系统环境变量配置*defaultProperties默认配置,如果通过

  1. setDefaultProperties()

方法指定了则有
这个时候通过下面3种方式执行扩展的业务逻辑

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

加入的json类型的配置先于配置文件的加载,因此

  1. SPRING_APPLICATION_JSON

  1. spring.application.json

放在配置文件(如application.yml文件)中是不能生效的。
所有

  1. EnvironmentPostProcessor

实现执行完后,新增的配置如下表,这些配置中除

  1. spring.application.json

外,其它的优先级都在上面表格所述资源的后面。
配置类型名称说明*spring.application.json以

  1. SPRING_APPLICATION_JSON

  1. spring.application.json

为键配置的json格式数据。
对于servlet应用其优先级会排在

  1. servletConfigInitParams

前面,其它情况会排在

  1. systemProperties

前面*random提供以

  1. random.

为前缀的随机值配置值获取*[名称不固定,根据资源类型生成]配置文件中提供的配置*[Config resource ‘class path resource [application.yml]’ via location ‘optional:classpath:/’]application.yml配置文件中提供的配置*[META-INF/spring.integration.properties]

  1. META-INF/spring.integration.properties

中配置
对于

  1. application.yml

及格式为

  1. application-${profile}.yml

文件,一个文件一个配置类型名称,比如classpath下有如下配置文件

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

如果

  1. spring.profiles.active=dao,app,mid

, 则生成的

  1. PropertySource

名称和优先级顺序如下。

  1. spring.profiles.active

中配置越后面的profile优先级越高。不过默认配置文件

  1. 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 通过设置以

  1. spring.main

为前缀的配置来设置

  1. SpringApplication

对象的属性

  1. bindToSpringApplication

方法的源码如下:

  1. // 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);}}

如果要通过配置来影响

  1. SpringApplication

的属性,提供

  1. spring.main

为前缀的配置即可. 如在

  1. application.yml

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

  1. spring:main:allowCircularReferences:trueallowBeanDefinitionOverriding:true

上面示例中

  1. allowCircularReferences

  1. allowBeanDefinitionOverriding

都是

  1. SpringApplication

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

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

  1. SpringApplication

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

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

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

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

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

  1. SpringApplication

的象模板方法

  1. run

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

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

2.1创建Spring容器ConfigurableApplicationContext

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

说明:

  1. applicationContextFactory

可通过

  1. SpringApplication#setApplicationContextFactory

方法指定。
其默认为

  1. DefaultApplicationContextFactory

2.1.1 使用

  1. DefaultApplicationContextFactory

创建不同应用的spring容器

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

  1. DefaultApplicationContextFactory

  1. create

方法。

  1. // 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中可以创建哪些类型的容器?

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

  1. ApplicationContextFactory

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

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

上面

  1. ApplicationContextFactory

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

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

注意目前容器创建的环境对象,与前面spring boot的

  1. SpringApplication

创建的不一样。后面步骤spring boot会把自身的环境对象覆盖此处创建的环境对象,
同时也会更新scanner和reader持有的环境对象。为什么spring容器会重复创建环境对象呢?可以从spring
boot和spring容器属于两个不同的问题域进行分析,这里不做深入探讨。

2.1.3.1
  1. AnnotatedBeanDefinitionReader

  1. ClassPathBeanDefinitionScanner

使用环境对象干什么?

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

  1. ConditionEvaluator

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

  1. BeanDefinition

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

  1. ConditionEvaluator

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

  1. ConditionContext

对象。有些

  1. Condition

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

  1. ConditionEvaluator

本质是执行

  1. Condition

对象的

  1. match

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

  1. Condition

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

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

在执行

  1. match

方法前,

  1. ConditionEvaluator

从类或方法的

  1. Conditional

注解获得

  1. Condition

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

  1. Conditional

注解。 比如下面的

  1. ConditionalOnClass

注解。
下面代码中

  1. OnClassCondition

类实现了

  1. Condition

接口,它是

  1. ConditionalOnClass

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

  1. // 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 创建
  1. AnnotatedBeanDefinitionReader

对象会初始化哪些配置?

  1. AnnotatedBeanDefinitionReader

用于为使用

  1. @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 创建
  1. ClassPathBeanDefinitionScanner

对象会初始化哪些配置?

  1. ClassPathBeanDefinitionScanner

用于扫描类上有spring

  1. @Component

注解或Jakarta/java EE的

  1. @ManagedBean

  1. @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套扩展方式,一套是基于事件的监听器、另一套是扩展接口。为了加载这些扩展类,又提供了各种灵活多样的加载方式:
注解、配置文件、系统配置、环境配置、命令行命令、

  1. META-INF/spring.factories

、代码配置。

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

  1. @SpringBootApplication

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

  1. SpringApplication

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

  1. // spring boot SpringApplication 源码privatevoidprepareContext(DefaultBootstrapContext bootstrapContext,ConfigurableApplicationContext context,ConfigurableEnvironment environment,SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments,Banner printedBanner){// 把spring boot创建的环境对象交给spring容器
  2. context.setEnvironment(environment);// 设置resourceLoader、bean名称生成器beanNameGenerator、类型转换服务ConversionServicepostProcessApplicationContext(context);// addAotGeneratedInitializerIfNecessary(this.initializers);// 2.2执行ApplicationContextInitializer初始化Spring容器applyInitializers(context);// 2.3容器已准备好,事件通知,可发布ApplicationContextInitializedEvent事件标识容器已初始化
  3. listeners.contextPrepared(context);// 2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件
  4. bootstrapContext.close(context);if(this.logStartupInfo){logStartupInfo(context.getParent()==null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 把封装了启动参数的对象注册到bean工厂,供其它bean依赖注入使用
  5. beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if(printedBanner !=null){
  6. beanFactory.registerSingleton("springBootBanner", printedBanner);}if(beanFactory instanceofAbstractAutowireCapableBeanFactory autowireCapableBeanFactory){// 设置是否允许循环依赖
  7. autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);if(beanFactory instanceofDefaultListableBeanFactory listableBeanFactory){// 设置是否允许覆盖bean定义
  8. listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}if(this.lazyInitialization){// 延迟初始化
  9. context.addBeanFactoryPostProcessor(newLazyInitializationBeanFactoryPostProcessor());}if(this.keepAlive){// 会创建一个非守护线程,该线程一直等待到spring容器发出ContextClosedEvent事件时才结束
  10. context.addApplicationListener(newKeepAlive());}// 添加bean工厂后处理器,保证默认配置优先级最低, 即名称defaultProperties的PropertySource
  11. 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事件
  12. 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添加

  1. ConfigurationWarningsPostProcessor

工厂后处理器用于检查

  1. @ComponentScan

的包是否正确DelegatingApplicationContextInitializer代理

  1. context.initializer.classes

配置的初始化器对象

2.2.2 实现

  1. ApplicationContextInitializer

接口可以做些什么扩展?

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

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

如果需要注册自定义的初始化器,则需要在

  1. META-INF/spring.factories

文件中以properties格式添加即可,示例如下:

  1. # 示例Application Context Initializers
  2. org.springframework.context.ApplicationContextInitializer=com.example.context.MyApplicationContextInitializer

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

所有

  1. ApplicationContextInitializer

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

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

2.4关闭DefaultBootstrapContext并发布BootstrapContextClosedEvent事件

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

  1. BootstrapContext

,默认实现为

  1. DefaultBootstrapContext


这个

  1. BootstrapContext

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

  1. BootstrapContextClosedEvent

事件通知其监听器

  1. ApplicationListener<BootstrapContextClosedEvent>

用于处理收尾工作。
这个监听器的通常通过

  1. BootstrapRegistryInitializer

扩展添加,
该扩展在

  1. DefaultBootstrapContext

创建后执行。示例如下:

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

2.5加载主要资源的BeanDefinition

2.5.1 哪些可以作为主要资源

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

  1. // spring boot SpringApplication 源码publicSet<Object>getAllSources(){Set<Object> allSources =newLinkedHashSet<>();// 2.5.1.1 primarySources类型为Class, 代表主要类if(!CollectionUtils.isEmpty(this.primarySources)){
  2. allSources.addAll(this.primarySources);}// 2.5.1.2 sources类型为String, 可代表类、路径、包名if(!CollectionUtils.isEmpty(this.sources)){
  3. allSources.addAll(this.sources);}returnCollections.unmodifiableSet(allSources);}

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

2.5.1.1
  1. primarySources

介绍

  1. primarySources

定义为

  1. private final Set<Class<?>> primarySources

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

  1. @SpringBootApplication

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

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

另外,

  1. primarySources

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

2.5.1.2
  1. sources

介绍

定义为

  1. private Set<String> sources

,通常为

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

指定source的示例代码如下

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

2.5.2 加载资源的目的和作用

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

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

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

  1. // 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方法,如下源码

  1. // 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定义为例

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

下面是

  1. AnnotatedBeanDefinitionReader

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

  1. // 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;}
  2. 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添加代理配置
  3. 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容器

  1. ApplicationContext

的refresh方法实现在抽象类

  1. 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任意一个接口就可以获得命令行传参,并使用

  1. @Component

注解等方式让spring管理。
下面是spring boot执行ApplicationRunner和CommandLineRunner接口的代码,这个没什么好解释的。

  1. // 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){
  2. instancesToBeanNames.put(beanFactory.getBean(beanName,Runner.class), beanName);}// 对bean进行排序:通过@Priority、@Order、PriorityOrdered接口、Order提供排序值Comparator<Object> comparator =getOrderComparator(beanFactory).withSourceProvider(newFactoryAwareOrderSourceProvider(beanFactory, instancesToBeanNames));
  3. 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 异常处理

从创建环境对象开始到执行

  1. 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启动过程详解”的评论:

还没有评论