共计 18902 个字符,预计需要花费 48 分钟才能阅读完成。
本文已收录【修炼内功】跃迁之路
微信关注“林中小舍”,林小二带你聊技术!
上篇文章 Spring Framework 中的注解是如何运作的 介绍了 Spring Framework 中各种注解的运作形式,(在 SpringBoot 推出之前)对于组件的应用还须要手动进行配置(无论 xml/groovy 文件形式还是注解形式),如 DataSource、SessionFactory、TransactionManager、DispatcherServlet 等等
SpringBoot 提供了一种新的形式用于简化 Spring 利用的搭建及开发过程,通过各种 starter 实现组件的主动拆卸(autoconfig),将约定优于配置体现的酣畅淋漓,在此基础上还提供了泛滥扩大点用以批改约定的配置及默认的组件性能
那,SpringBoot 是如何实现主动拆卸的?(SpringBoot 提供的 starter 列表见 Using boot starter)
疾速生成 Boot 利用
- SpringBoot 脚手架:https://start.spring.io/
- PandoraBoot 脚手架(阿里外部):http://mw.alibaba-inc.com/boo…
SpringBoot 的启动
SpringBoot 利用个别会将 SpringApplication 及 @SpringBootApplication 放一起配合应用
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication app = new SpringApplication(Application.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
// 或者
// SpringApplication.run(Application.class, args);
// 或者
// new SpringApplicationBuilder(Application.class)
// .profiles("dev")
// .properties("spring.application.name=spring-boot-demo")
// .run(args);
}
}
这里,不肯定非要应用 @SpringBootApplication 注解,SpringApplication也不肯定非要放在 main 入口中
在此,将 SpringApplication 及 @SpringBootApplication 的作用分两个小结详解
上例中展现了三种 SpringApplication 的编码方式,但不管何种形式都离不开两个步骤:创立(SpringApplication#new);运行(SpringApplication#run)
SpringApplication 创立
在 SpringApplication 创立过程中初始化了一些参数,各参数的作用 / 应用会在下文中得以解释
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// ResourceLoader
this.resourceLoader = resourceLoader;
// 主入口类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// web 类型 NONE | SERVLET | REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置 initializers(org.springframework.context.ApplicationContextInitializer)setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置 listeners(org.springframework.context.ApplicationListener)setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// main 函数所处的类
this.mainApplicationClass = deduceMainApplicationClass();}
这里须要着重介绍的是在初始化某些参数时应用的一些逻辑
web 类型的探测
web 类型的探测用到了 WebApplicationType#deduceFromClasspath,其逻辑如下
或者你也遇到过相似的状况,自身并不是一个 web 工程,却因为某些起因(如引入第三方包的不标准)导致探测到了 REACTIVE/SERVLET 从而进入了 web 启动流程(ReactiveWebServerApplicationContext / ServletWebServerApplicationContext)进而引发其余的问题
spring.factories 文件初探
Java 中 SPI(Service Provider Interface)的概念或者各位都不生疏,其提供了一套用来被第三方实现或者扩大的 API,用以启用框架扩大、替换组件,将拆卸的控制权移到程序之外(如最为典型的 jdbc dirver)
Spring 中大量应用了该思维,spring.factories 是其一种实现模式,spring.factories 外围加载逻辑在 SpringFactoriesLoader#loadFactoryNames / SpringFactoriesLoader#loadSpringFactories
以 spring-boot 中 spring.factories 片段为例
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
SpringFactoriesLoader#loadSpringFactories 加载后的 Map,key 为等号前的接口类,value 为等号后的实现类列表
再次回到 SpringApplication 创立过程中对 initializers
及listeners
的初始化,其别离加载并初始化了 spring.factories 文件中的 org.springframework.context.ApplicationContextInitializer 及org.springframework.context.ApplicationListener
Java SPI 及 Spring Factories 的几点比照如下
Java SPI | Spring Factories | |
---|---|---|
文件地位 | META-INF/services/{interface full class name} | META-INF/spring.factories |
文件内容 | 每行一个接口实现类,能够多行 | properties 文件格式,key 为接口类全门路,value 为实现类全门路(多个间应用逗号分隔) |
加载办法 | ServiceLoader#load | SpringFactoriesLoader#loadFactoryNames/#loadFactories |
Q&A
- 是否能够自定义 META-INF/spring.factories,用以扩大 Spring 中用到的组件?
能够,事实上 Spring 也激励这样做
- 是否能够应用 META-INF/spring.factories 实现本人的逻辑?
能够,SpringFactoriesLoader 中的办法为公开的,并且 SpringFactoriesLoader 的实现在 spring-core 中,其应用并不局限于 spring-boot
注:应用 SpringFactorieLoader 加载并初始化的实例通常并没有被 IOC 治理,通常也没有实现依赖注入的能力
参考 SpringFactoriesLoader#loadFactories / SpringApplication#createSpringFactoriesInstances
主 main 函数入口类探测
mainApplicationClass 用以记录 main 函数所在类,其探测实现在 SpringApplication#deduceMainApplicationClass,逻辑则略显“奇巧”,通过结构 RuntimeException 获取其 stackTrace,剖析每一个 StackTraceElement 找到调用链上的 main 函数,并返回该函数所在类
对于 mainApplicationClass 的作用会在下文介绍
SpringApplication 启动
SpringApplication 的启动过程要比创立过程切实的多
SpringApplicationRunListener 配置在 spring.factories 中,其能够针对 SpringBoot 启动过程中的各事件进行监听(详见 SpringApplicationRunListener 中的办法定义)
Spring 盘根错节的逻辑关系难以在一张流程图中理清所有,常规,将其拆分并各个击破
ApplicationArguments
在启动 jvm 时通常能够增加一些启动参数,诸如 -jar
、-XX:CMSTriggerRatio
、-Dproperty
以及自定义参数等
通常有两种参数能够被 Spring 应用,-Dproperty=value 及 –property=value
前者为 jvm 的能力,其将具体的参数值设置到零碎参数中,以便任意的框架均可通过 System#getProperty 获取
-Dproperty=value
Sets a system property value. The property variable is a string with no spaces that represents the name of the property. The value variable is a string that represents the value of the property. If value is a string with spaces, then enclose it in quotation marks (for example
-Dfoo="foo bar"
).https://docs.oracle.com/javas…
后者为 spring 的能力,其底层应用 SimpleCommandLinePropertySource 来解析,在 SpringBoot 利用中还能够通过 bean-type 或 bean-name(“springApplicationArguments”)来注入 ApplicationArguments,以便在本人的逻辑中获取 –property=value类型的启动参数
结构 Environment
org.springframework.boot.SpringApplication#prepareEnvironment
流程图中 ConversionService 的作用能够参考 org.springframework.beans.TypeConverter 的应用(Bean 是如何被创立的)
同时 Spring 会将启动参数中的 -Dproperty=value 及 –property=value一并合并到 Environment 中
Environment 创立过程中会将 SystemProperties 及 SystemEnvironment 一并合并到 Environment 中
见 AbstractEnvironment#new 及 StandardEnvironment#customizePropertySources
留神到,application 配置文件中 spring.main.* 下的配置能够批改 SpringApplication 中的参数,如
spring.main.log-startup-info=false
spring.main.lazy-initialization=true
spring.main.banner-mode=LOG
# 见 SpringApplication 中反对的参数设置
# https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features
Binder 的应用见 Property Binding in Spring Boot 2.0
输入 Banner
org.springframework.boot.SpringApplication#printBanner
通常在 Spring 启动时会看到诸如下图的 banner,其实 spring 是提供了弱小定制能力的,具体应用能够参考 Customizing the Banner
Spring 同时反对文本(间接输入文本内容)及图片(将图片解析为文本进行绘制)两种 banner 能力,其别离通过 ResourceBanner 及 ImageBanner 实现,具体见 SpringApplicationBannerPrinter#getBanner
结构 ApplicationContext
org.springframework.boot.SpringApplication#createApplicationContext
这里并没有太多须要介绍的,spring 会依据 webApplicationType 生成不同的 ApplicationContext
webApplicationType | ApplicationContext |
---|---|
WebApplicationType.SERVLET | AnnotationConfigServletWebServerApplicationContext |
WebApplicationType.REACTIVE | AnnotationConfigReactiveWebServerApplicationContext |
默认 | AnnotationConfigApplicationContext |
ApplicationContext 初始化前
org.springframework.boot.SpringApplication#prepareContext
这里须要重点介绍几个逻辑
ApplicationContextInitializer#initialize
文章开始初介绍了 Spring 会从 spring.factories 中加载 ApplicationContextInitializer,在这里则会用到它们的 initialize 办法,通常能够通过该办法注册一些额定的 bean 或者 BeanFactoryPostProcessor,甚至对 ApplicationContext 做一些批改
如 spring-boot-autoconfiguration 中注册的 SharedMetadataReaderFactoryContextInitializer
class SharedMetadataReaderFactoryContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor());
}
}
再如 spring-boot-devtools 中注册的 RestartScopeInitializer
public class RestartScopeInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
}
}
lazyInitialization
如果该属性被设置为 true,spring 则会向 ApplicationContext 中增加 LazyInitializationBeanFactoryPostProcessor,其会将所有 bean 的 lazyInit 属性设置为 true,以提早 bean 的加载并放慢 SpringBoot 的启动速度,其弊病则是将一些隐性的谬误提早到了首次加载阶段(如注入问题)
A downside of lazy initialization is that it can delay the discovery of a problem with the application.
当然也有例外情况,可通过注册 LazyInitializationExcludeFilter 进行排除实现
primarySources 及 sources 的加载
primarySources 可通过 SpringApplication 的结构传入,sources 可通过 spring.main.sources 设置
通常咱们须要将 @SpringBootApplication 注解增加到 primarySource 或 source 类上,其目标就是在加载 primarySources 或 sources 时一并解析
SpringApplication 的启动逻辑(run 函数)并不是 SpringBoot 的全副,其大部分性能由 @SpringBootApplication 注解代入
至此还没有执行 ApplicationContext 的 refresh 动作,也就是说 primarySources 及 sources(如果存在则包含 @SpringBootApplication)的解析是优先于 ApplicationContext#refresh 的
当然,实践上能够将 @SpringBootApplication 注解放到其余类上,但请保障该类能够被 Spring 扫描到
ApplicationContext 的初始化
org.springframework.boot.SpringApplication#refreshContext
该局部没有非常特地的逻辑,执行 ApplicationContext 的 refresh 办法并注册 shutdown hook(registerShutdownHook)
ApplicationContext 的 refresh 逻辑参考 ApplicationContext 给开发者提供了哪些 (默认) 扩大
ApplicationContext 初始化后
org.springframework.boot.SpringApplication#afterRefresh
该局部并没有理论的实现,由子类实现
异样解决
org.springframework.boot.SpringApplication#handleRunFailure
在 SpringApplication 启动过程中产生异样则有此局部解决,这里须要关注的是,具体的异样可由 SpringBootExceptionReporter#reportException 接管并解决
而 SpringBootExceptionReporter 则由 spring.factories 中的 org.springframework.boot.SpringBootExceptionReporter 进行注册(通常为 org.springframework.boot.diagnostics.FailureAnalyzers)
至此实现了 SpringApplication 的初始化及执行过程的介绍,但对于泛滥 SpringBoot 的能力貌似都没有体现,正如上文所述,SpringBoot 大部分性能由 @SpringBootApplication 注解代入
@SpringBootApplication 注解的威力
如上文介绍,通常须要将 @SpringBootApplication 注解增加到 primarySource 或 source 类上,其目标是在加载 primarySources 或 sources 时一并解析,并且此过程个别在 ApplicationContext refresh 之前
对于注解的解析逻辑见 Spring Framework 中的注解是如何运作的
首先看一下 @SpringBootApplication 注解的层级构造咱们对其进行拆分并顺次介绍其作用
ComponentScan
在 Spring Framework 中的注解是如何运作的一文中有介绍,如果 @ComponentScan 中没有指定任何扫描 packages,Spring 则会应用指标 class 所在的 package 进行扫描,这也是为什么咱们在 @SpringBootApplication 上没有指定 scanBasePackages 时,总是会扫描 @SpringBootApplication 润饰类所在 package 的起因
此外,@SpringBootApplication 中默认为 @ComponentScan 增加了两个 excludeFilter,TypeExcludeFilter及AutoConfigurationExcludeFilter
前者的排除规定由 TypeExcludeFilter 类型的 bean 实现,可自行实现并注册
后者则为了避免 primarySource 与 spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 同时指定一个类时的反复注册
SpringBootConfiguration
该注解仅是简略继承了 @Configuration,@Configuration 介绍详见 Spring Framework 中的注解是如何运作的
EnableAutoConfiguration
@EnableAutoConfiguration 注解次要用来发现须要拆卸的配置类,并进行主动拆卸
该注解应用 @Import 间接导入了 AutoConfigurationImportSelector,同时继承了 @AutoConfigurationPackage,而 @AutoConfigurationPackage 也简略地应用 @Import 导入了 AutoConfigurationPackages.Registrar
这里咱们对导入的两个类进行剖析(@Import 的介绍详见 Spring Framework 中的注解是如何运作的)
AutoConfigurationImportSelector
AutoConfigurationImportSelector#selectImports
该注册器的逻辑其实非常简单
- spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 记录了所有须要注册的配置类
- @EnableAutoConfiguration 中的 exclude 及 excludeName 参数,及 spring.autoconfigure.exclude,配置了不须要注册的配置类信息
- spring-autoconfigure-metadata.properties 中记录了各种配置类的注册条件
依据以上信息,能够筛选出最终须要注册的配置类,并对其进行配置
spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration,示例如
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
spring-boot-autoconfigure 工程中的 spring.factories 文件中定义了大量的 XxxAutoConfiguration,用以在引入不同 starter 时对相干 Bean 的主动拆卸(SpringBoot 提供的 starter 列表见 Using boot starter)
spring-boot-autoconfigure 工程中的 spring.factories 文件定义了 spring-boot 绝大部分 starter 主动拆卸的 XxxAutoConfiguration,但通常来讲并不是所有的 XxxAutoConfiguration 都须要拆卸
决定一个 XxxAutoConfiguration 是否须要进行拆卸,由以下几方面管制
- @EnableAutoConfiguration 参数管制
- spring.autoconfigure.exclude 参数管制
- spring-autoconfigure-metadata.properties 文件中指定的规定管制
- XxxAutoConfiguration 自身的条件管制,如硬编码的 @ConditionalOn
spring-autoconfigure-metadata.properties 文件,示例如
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureOrder=-2147483648
该文件定义了各种配置类主动拆卸的条件,properties 文件的格局为 ${配置类全门路}.${条件类型}=${条件值}
条件类型能够为 ConditionalOnClass、AutoConfigureAfter、AutoConfigureOrder 等,其逻辑由 org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 定义解析,AutoConfigurationImportFilter 同样在 spring.factories 中指定
Q: 如何借助 spring.factories 在 SpringBoot 启动时主动拆卸自定义的 Bean(s)? 如何应用该个性自定义 starter?
Q&A:
SpringBoot 是如何通过 @AutoConfigureBefore、@AutoConfigureAfter、AutoConfigureOrder 管制配置类加载程序的?
AutoConfigurationImportSelector 实现了 DeferredImportSelector(DeferredImportSelector 的原理参考 Spring Framework 中的注解是如何运作的),其在 ConfigurationClassParser#deferredImportSelectorHandler.process() 阶段,应用 AutoConfigurationSorter 针对以上注解对配置类进行了加载的排序
- AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports
- AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations
AutoConfigurationPackages.Registrar
AutoConfigurationPackages.Registrar#registerBeanDefinitions
该注册器的逻辑则略显薄弱,其注册了一个名为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 类型为 BasePackages 的 bean,该 bean 中记录了被 @EnableAutoConfiguration 润饰的类所在的 package
ConfigurationPropertiesScan
@ConfigurationPropertiesScan 次要用来发现 @ConfigurationProperties,并对其进行解析、注册
同样的,该注解应用 @Import 间接导入了 ConfigurationPropertiesScanRegistrar,同时继承了 @EnableConfigurationProperties,而 @EnableConfigurationProperties 也简略地应用 @Import 导入了 @EnableConfigurationPropertiesRegistrar
这里咱们对导入的两个类进行剖析
ConfigurationPropertiesScanRegistrar
ConfigurationPropertiesScanRegistrar#registerBeanDefinitions
该局部逻辑比较简单,不再附以流程图
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 从 @ConfigurationPropertiesScan 中获取须要扫描 packages
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 在指定 packages 中扫描被 @ConfigurationProperties 润饰的类并注册
scan(registry, packagesToScan);
}
所以整体逻辑
- 从 @ConfigurationPropertiesScan 中(basePackages/basePackageClasses)获取须要扫描的 packages,如果均为指定则默认 @ConfigurationPropertiesScan 润饰类所在 package
- 在以上 package 中扫描被 @ConfigurationProperties 润饰的类并注册,扫描器由 ClassPathScanningCandidateComponentProvider 创立
以上具体逻辑可浏览源码,扫描逻辑可参考 Spring Framework 中的注解是如何运作的 – ClassPathBeanDefinitionScanner
EnableConfigurationPropertiesRegistrar
EnableConfigurationPropertiesRegistrar#registerBeanDefinitions
@ConfigurationPropertiesScanRegistrar 只是用来在指定 packages 中发现 @ConfigurationProperties 并注册,那 @ConfigurationProperties 的具体解析在哪里?
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册处理器
registerInfrastructureBeans(registry);
// 对 @EnableConfigurationProperties 中指定的类进行注册
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
EnableConfigurationPropertiesRegistrar 做了两件事
- @EnableConfigurationProperties#value 能够间接指定被 @ConfigurationProperties 润饰的类,EnableConfigurationPropertiesRegistrar 对其进行注册(注册逻辑可参考 Spring Framework 中的注解是如何运作的 – AnnotatedBeanDefinitionReader)
-
注册一些处理器(以下仅列出其中比拟要害的几个处理器)
- ConfigurationPropertiesBindingPostProcessor
- ConfigurationPropertiesBinder
ConfigurationPropertiesBinder
ConfigurationPropertiesBinder 是一个通用能力,底层采纳 Binder(见 Property Binding in Spring Boot 2.0)将 @ConfigurationProperties 中指定的规定属性绑定到润饰的 Bean 实例中
ConfigurationPropertiesBindingPostProcessor
ConfigurationPropertiesBindingPostProcessor 实现了BeanPostProcessor,在其 postProcessBeforeInitialization 函数中借助 ConfigurationPropertiesBinder 对 @ConfigurationProperties 润饰的 Bean 做属性绑定动作(BeanPostPocessor 原理参考 Bean 是如何被创立的)
通常 @ConfigurationProperties 与 @NestedConfigurationProperty 配合应用,解决简单对象属性绑定
参考 Nested Properties
starter 中应用 @ConfigurationProperties 绑定属性时,为了疾速让使用者理解 starter 能够进行配置的选项,以及辅助 IDE 在配置时进行提醒、校验、主动补全等,会应用 spring-boot-configuration-processor 将 @ConfigurationProperties 所在类的信息映射为元数据文件(META-INF/spring-configuration-metadata.json)
其中形容信息来自规范的 javadoc(参考 Generating Your Own Metadata by Using the Annotation Processor)
回顾
回顾一下 @SpringBootApplication 注解的层级构造
Spring Framework 中的注解是如何运作的中有介绍 Spring 中的注解编程模型,@SpringBootApplication 由很多注解组合而成,并且在其中重写了 @EnableAutoConfiguration 及 @ComponentScan 的参数,以便在 @SpringBootApplication 中间接管制后者的参数
是否有曾思考过,@SpringBootApplication 所蕴含的注解只能以 @SpringBootApplication 的形式应用么?是否能够独自应用?@SpringBootApplication 并没有重写 @ConfigurationPropertiesScan 及 @EnableConfigurationProperties 注解参数,那后者的参数如何管制?
凡事不必太过死板,@SpringBootApplication 仅仅整合了多个注解的能力,至于组合应用还是独自应用齐全由开发者决定,这也是 Spring 比拟敌对的中央
对于 Bean 属性绑定而言,反而比拟罕用的是 @ConfigurationProperties 与 @EnableConfigurationProperties 的组合,而不是 @SpringBootApplication,尤其在 starter 的实现中
@ConfigurationProperties(prefix = "spring.my", ignoreInvalidFields = true)
public class MyProperties {...}
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyConfiguration {...}
Q: 是否还有其余发现?
spring.factories 浅解
SpringBoot 中 spring.factories 内容十分丰盛,这里筛选几个有代表性的进行梳理,更多的内容还须要读者自行摸索
spring-boot
SpringApplicationRunListener
org.springframework.boot.SpringApplicationRunListener (on spring.factories)
上文有介绍,SpringApplicationRunListener 能够针对 SpringBoot 启动过程中的各事件进行监听,其中比拟有代表性的 EventPublishingRunListener
,其在contextLoaded 阶段(EventPublishingRunListener#contextLoaded)将 spring.factories 中配置的 ApplicationListener 增加到创立的 ApplicationContext 中
ApplicationListener
org.springframework.context.ApplicationListener (on spring.factories)
ApplicationListener 的应用在 ApplicationContext 给开发者提供了哪些 (默认) 扩大中有介绍,ApplicationListener 的注册也能够在 spring.factories 中,这里要介绍的一个 ApplicationListener 是 ConfigFileApplicationListener
,其在ApplicationEnvironmentPreparedEvent 事件(ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent)下加载了 spring.factories 中所有的 EnvironmentPostProcessor,执行其postProcessEnvironment 办法对 Environment 进行加强
EnvironmentPostProcessor
org.springframework.boot.env.EnvironmentPostProcessor (on spring.factories)
从命名上能够看出,EnvironmentPostProcessor 是在 Environment 筹备好后对 Environment 做一些解决
ConfigFileApplicationListener
除了实现 ApplicationListener 之外同时还实现了 EnvironmentPostProcessor,在其 postProcessEnvironment 办法中加载了 spring.factories 中所有的 PropertySourceLoader,将 classpath:/,classpath:/config/,file:./,file:./config/ 下的 application.* 相似的文件加载到 Environment 中
PropertySourceLoader
org.springframework.boot.env.PropertySourceLoader
PropertySourceLoader 则是用来加载配置文件的具体实现,SpringBoot spring.factories 中提供了两种实现 PropertiesPropertySourceLoader 及 YamlPropertySourceLoader,前者用来加载 properties 及xml文件,后者用来加载 yml 文件
从这里能够看出,通过 (on spring.factories)
- EventPublishingRunListener(SpringApplicationRunListener)->
- ConfigFileApplicationListener(ApplicationListener)->
- ConfigFileApplicationListener(EnvironmentPostProcessor)->
- Properties/YamlPropertySourceLoader(PropertySourceLoader)
才实现了 application.properties / application.yml 配置文件的加载
spring.factories 所提供服务的体系比拟宏大,其在 SpringBoot 主动拆卸中起到了至关重要的作用
Q: 如何实现本人的 PropertySourceLoader,实现自定义 application.* 文件的加载
Q: 理解 SpringBoot 提供的 spring.factories 内的性能,并对其进行扩大
spring-autoconfigure
EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration
用来定义须要导入的配置类,SpringBoot starter 应用该形式实现配置类的主动导入
AutoConfigurationImportFilter
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
用来实现相似 @Conditional 的性能,判断指标配置类是否满足导入条件
SpringBoot 提供了 OnBeanCondition、OnClassCondition、OnWebApplicationCondition 三种 Condition,别离解决
- OnBeanCondition
@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate
- OnClassCondition
@ConditionalOnClass、@ConditionalOnMissingClass
- OnWebApplicationCondition
@ConditionalOnWebApplication、@ConditionalOnNotWebApplication
等注解的解决
后记
SpringBoot 的内容远不止这些,SpringBoot 所能实现的性能也远超我目前对它的意识,Spring 很弱小,但也有它本身的毛病,当下不乏一些优良的竞争对手
语言与框架只是你迈高低一级台阶的工具和伎俩,匠人精力不能少,但也要正确看待当今疾速变动的世界