上一篇分析了SpringBoot的自动配置的相关源码,自动配置主要有以下几个重要的步骤:
从spring.factories配置文件中加载自动配置类;
加载的自动配置类中排除掉
@EnableAutoConfiguration
注解的exclude
属性指定的自动配置类;然后再用
AutoConfigurationImportFilter
接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass
,@ConditionalOnBean
和@ConditionalOnWebApplication
的条件,若都符合的话则返回匹配结果;然后触发
AutoConfigurationImportEvent
事件,告诉ConditionEvaluationReport
条件评估报告器对象来分别记录符合条件和exclude
的自动配置类。最后spring再将最后筛选后的自动配置类导入IOC容器中
继续来分析SpringBoot的自动配置的相关源码,我们来分析下@EnableConfigurationProperties
和@ConfigurationProperties
这两个注解,来探究下外部配置属性值是如何被绑定到@ConfigurationProperties注解的类属性中的?
Example:以配置web项目的服务器端口为例,若我们要将服务器端口配置为
9999
,那么需要在application.properties
配置文件中配置server.port=9999
,此时该配置值9999
就将会绑定到被@ConfigurationProperties
注解的类ServerProperties
的属性port
上,从而使得配置生效。
1.@EnableConfigurationProperties
以前面的设置服务器端口的例子来分析,先直接来看看ServerProperties
的源码,应该能找到源码的入口:
1 | "server", ignoreUnknownFields = true) (prefix = |
可以看到,ServerProperties
类上标注了@ConfigurationProperties
这个注解,服务器属性配置前缀为server
,是否忽略未知的配置值(ignoreUnknownFields
)设置为true
。
那么再来看下@ConfigurationProperties
这个注解的源码:
1 | @Target({ ElementType.TYPE, ElementType.METHOD }) |
@ConfigurationProperties
这个注解的作用就是将外部配置的配置值绑定到其注解的类的属性上,可以作用于配置类或配置类的方法上。可以看到@ConfigurationProperties
注解除了有设置前缀,是否忽略一些不存在或无效的配置等属性等外,这个注解没有其他任何的处理逻辑,可以看到@ConfigurationProperties
是一个标志性的注解,源码入口不在这里。
这里讲的是服务器的自动配置,来看下自动配置类ServletWebServerFactoryAutoConfiguration
的源码:
1 | @Configuration |
可以看到,ServletWebServerFactoryAutoConfiguration
自动配置类中有一个@EnableConfigurationProperties
注解,且注解值是前面讲的ServerProperties.class
,因此@EnableConfigurationProperties
注解肯定就是关注的重点了。
再来看下@EnableConfigurationProperties
注解的源码:
1 |
|
@EnableConfigurationProperties
注解的主要作用就是为@ConfigurationProperties
注解标注的类提供支持,即对将外部配置属性值(比如application.properties配置值)绑定到@ConfigurationProperties
标注的类的属性中。
SpringBoot源码中还存在了
ConfigurationPropertiesAutoConfiguration
这个自动配置类,同时spring.factories
配置文件中的EnableAutoConfiguration
接口也配置了ConfigurationPropertiesAutoConfiguration
,这个自动配置类上也有@EnableConfigurationProperties
这个注解,堆属性绑定进行了默认开启。
那么,@EnableConfigurationProperties
这个注解对属性绑定提供怎样的支持呢?
可以看到@EnableConfigurationProperties
这个注解上还标注了@Import(EnableConfigurationPropertiesImportSelector.class)
,其导入了EnableConfigurationPropertiesImportSelector
,因此可以肯定的是@EnableConfigurationProperties
这个注解对属性绑定提供的支持必定跟EnableConfigurationPropertiesImportSelector
有关。
接下来分析EnableConfigurationPropertiesImportSelector
,是如何承担将外部配置属性值绑定到@ConfigurationProperties
标注的类的属性中的。
2.EnableConfigurationPropertiesImportSelector
EnableConfigurationPropertiesImportSelector
类的作用主要用来处理外部属性绑定的相关逻辑,其实现了ImportSelector
接口的selectImports
方法可以向容器中注册bean。
那么,看下EnableConfigurationPropertiesImportSelector
覆写的selectImports
方法:
1 | class EnableConfigurationPropertiesImportSelector implements ImportSelector { |
可以看到EnableConfigurationPropertiesImportSelector
类中的selectImports
方法中返回的是IMPORTS
数组,而这个IMPORTS
是一个常量数组,值是ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
。即EnableConfigurationPropertiesImportSelector
的作用是向Spring容器中注册了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
这两个bean
。
在EnableConfigurationPropertiesImportSelector
类中没看到处理外部属性绑定的相关逻辑,其只是注册了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
这两个bean
,接下来我们再看下注册的这两个bean
类。
2.1 ConfigurationPropertiesBeanRegistrar
先来看下ConfigurationPropertiesBeanRegistrar
这个类的源码:
1 | public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { |
ConfigurationPropertiesBeanRegistrar
是EnableConfigurationPropertiesImportSelector
的内部类,其实现了ImportBeanDefinitionRegistrar
接口,覆写了registerBeanDefinitions
方法。可见,ConfigurationPropertiesBeanRegistrar
又是用来注册一些bean
definition
的,即也是向Spring
容器中注册一些bean。
在ConfigurationPropertiesBeanRegistrar
实现的registerBeanDefinitions
中,可以看到主要做了两件事:
- 调用
getTypes
方法获取@EnableConfigurationProperties
注解的属性值XxxProperties
; - 调用
register
方法将获取的属性值XxxProperties
注册到Spring
容器中,用于以后和外部属性绑定时使用。
来看下getTypes
方法的源码:
1 | private List<Class<?>> getTypes(AnnotationMetadata metadata) { |
getTypes
方法里面的逻辑很简单,即将@EnableConfigurationProperties
注解里面的属性值XxxProperties
(比如ServerProperties.class
)取出并装进List
集合并返回。
由getTypes
方法拿到@EnableConfigurationProperties
注解里面的属性值XxxProperties
(比如ServerProperties.class
)后,此时再遍历将XxxProperties
逐个注册进Spring
容器中,来看下register
方法:
1 | private void register(BeanDefinitionRegistry registry, |
2.2 ConfigurationPropertiesBindingPostProcessorRegistrar
可以看到ConfigurationPropertiesBindingPostProcessorRegistrar
类名字又是以Registrar
单词为结尾,说明其肯定又是导入一些bean
definition
的。直接看源码:
1 | public class ConfigurationPropertiesBindingPostProcessorRegistrar |
ConfigurationPropertiesBindingPostProcessorRegistrar
类的逻辑非常简单,主要用来注册外部配置属性绑定相关的后置处理器即ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
。
那么接下来再来探究下注册的这两个后置处理器又是执行怎样的后置处理逻辑呢?
2.2.1 ConfigurationBeanFactoryMetadata
先来看下ConfigurationBeanFactoryMetadata
类源码:
1 | public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor { |
ConfigurationBeanFactoryMetadata
这个后置处理器,其实现了BeanFactoryPostProcessor
接口的postProcessBeanFactory
方法,在初始化bean
factory
时将@Bean
注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用。
从上面代码可以看到ConfigurationBeanFactoryMetadata
类覆写的postProcessBeanFactory
方法做的事情就是将工厂Bean
(可以理解为@Configuration
注解的类)及其@Bean
注解的工厂方法的一些元数据缓存到beansFactoryMetadata
集合中,以便后续使用。
由上代码中我们看到了ConfigurationBeanFactoryMetadata
类的beansFactoryMetadata
集合类型是Map<String, FactoryMetadata>
,那么我们再来看下封装相关工厂元数据的FactoryMetadata
类:
1 | private static class FactoryMetadata { |
FactoryMetadata
仅有两个属性bean
和method
,分别表示@Configuration
注解的工厂bean
和@Bean
注解的工厂方法。
例如:
1 | /** |
为了更好理解上面beansFactoryMetadata
集合存储的数据是啥,最好动手调试看看里面装的是什么。总之这里记住一点就好了:ConfigurationBeanFactoryMetadata
类的beansFactoryMetadata
集合存储的是工厂bean
的相关元数据,以便在ConfigurationPropertiesBindingPostProcessor
后置处理器中使用。
2.2.2 ConfigurationPropertiesBindingPostProcessor
再来看下ConfigurationPropertiesBindingPostProcessorRegistrar
类注册的另外一个后置处理器ConfigurationPropertiesBindingPostProcessor
,这个后置处理器主要承担了将外部配置属性绑定到@ConfigurationProperties
注解标注的XxxProperties类的属性中(比如application.properties
配置文件中设置了server.port=9999
,那么9999
将会绑定到ServerProperties
类的port
属性中)的实现逻辑。
先来看下ConfigurationPropertiesBindingPostProcessor
的源码:
1 | public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, |
可以看到ConfigurationPropertiesBindingPostProcessor
后置处理器实现了两个重要的接口InitializingBean
和BeanPostProcessor
。
InitializingBean
接口的afterPropertiesSet
方法会在bean
属性赋值后调用,用来执行一些自定义的初始化逻辑比如检查某些强制的属性是否有被赋值,校验某些配置或给一些未被赋值的属性赋值。BeanPostProcessor
接口是bean
的后置处理器,其有postProcessBeforeInitialization
和postProcessAfterInitialization
两个勾子方法,分别会在bean
初始化前后被调用来执行一些后置处理逻辑,比如检查标记接口或是否用代理包装了bean
。
同时以上代码可以看到ConfigurationPropertiesBindingPostProcessor
后置处理器覆写了InitializingBean
的afterPropertiesSet
方法和BeanPostProcessor
的postProcessBeforeInitialization
方法。
接下来再来探究ConfigurationPropertiesBindingPostProcessor
后置处理器覆写的两个方法的源码。
2.2.2.1 在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器
先来分析下ConfigurationPropertiesBindingPostProcessor
覆写InitializingBean
接口的afterPropertiesSet
方法:
1 | // 这里主要是给 beanFactoryMetadata 和 configurationPropertiesBinder 的属性赋值,用于后面的后置处理器方法处理属性绑定的时候用 |
可以看到以上代码主要逻辑就是在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器,即从Spring
容器中获取到之前注册的ConfigurationBeanFactoryMetadata
对象赋给ConfigurationPropertiesBindingPostProcessor
后置处理器的beanFactoryMetadata
属性,还有就是新建一个ConfigurationPropertiesBinder
配置属性绑定器对象并赋值给configurationPropertiesBinder
属性。
再来看下ConfigurationPropertiesBinder
这个配置属性绑定器对象是如何构造的。
1 | ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) { |
可以看到在构造ConfigurationPropertiesBinder
对象时主要给其相关属性赋值(一般构造器逻辑都是这样):
- 给
applicationContext
属性赋值注入上下文对象; - 给
propertySources
属性赋值,属性源即外部配置值比如application.properties
配置的属性值,注意这里的属性源是由ConfigFileApplicationListener
这个监听器负责读取的。 - 给
configurationPropertiesValidator
属性赋值,值来自Spring
容器中名为configurationPropertiesValidator
的bean
。 - 给
jsr303Present
属性赋值,当javax.validation.Validator
,javax.validation.ValidatorFactory
和javax.validation.bootstrap.GenericBootstrap"
这三个类同时存在于classpath
中jsr303Present
属性值才为true
。
关于JSR303:
JSR-303
是JAVA EE 6中的一项子规范,叫做Bean Validation
,Hibernate Validator
是Bean Validation
的参考实现 。Hibernate Validator
提供了JSR 303
规范中所有内置constraint
的实现,除此之外还有一些附加的constraint
。
2.2.2.2 执行真正的外部属性绑定逻辑【主线】
前面分析了那么多,发现都还没到外部属性绑定的真正处理逻辑,前面步骤都是在做一些准备性工作,为外部属性绑定做铺垫。
在执行外部属性绑定逻辑前,准备好了相关元数据和配置属性绑定器后,此时再来看看ConfigurationPropertiesBindingPostProcessor
实现BeanPostProcessor
接口的postProcessBeforeInitialization
后置处理方法了,外部属性绑定逻辑都是在这个后置处理方法里实现。
直接看代码:
1 | // 因为是外部配置属性后置处理器,因此这里对@ConfigurationProperties注解标注的XxxProperties类进行后置处理完成属性绑定 |
ConfigurationPropertiesBindingPostProcessor
类覆写的postProcessBeforeInitialization
方法的做的事情就是将外部属性配置绑定到@ConfigurationProperties
注解标注的XxxProperties
类上,关键步骤总结如下:
- 从
bean
上获取@ConfigurationProperties
注解; - 若标注有
@ConfigurationProperties
注解的bean
,那么则进行进一步的处理:将外部配置属性值绑定到bean的属性值中后再返回bean
;若没有标注有@ConfigurationProperties
注解的bean
,那么将直接原样返回bean
。
注意:后置处理器默认会对每个容器中的
bean
进行后置处理,因为这里只针对标注有@ConfigurationProperties
注解的bean
进行外部属性绑定,因此没有标注@ConfigurationProperties
注解的bean
将不会被处理。
接下来再来看看外部配置属性是如何绑定到@ConfigurationProperties
注解的XxxProperties
类属性上的呢?
直接看代码:
1 | private void bind(Object bean, String beanName, ConfigurationProperties annotation) { |
前面分析了解到 ConfigurationBeanFactoryMetadata
覆写的postProcessBeanFactory
方法里已经将相关工厂bean
的元数据封装到ConfigurationBeanFactoryMetadata
类的beansFactoryMetadata
集合。
再来看下上面代码中的【1】getBeanType
和【2】getAnnotation
方法源码:
1 | private ResolvableType getBeanType(Object bean, String beanName) { |
注意到上面代码中的beanFactoryMetadata
对象没,ConfigurationPropertiesBindingPostProcessor
后置处理器的getBeanType
和getAnnotation
方法分别会调用ConfigurationBeanFactoryMetadata
的findFactoryMethod
和findFactoryAnnotation
方法,而ConfigurationBeanFactoryMetadata
的findFactoryMethod
和findFactoryAnnotation
方法又会依赖存储工厂bean
元数据的beansFactoryMetadata
集合来寻找是否有FactoryMethod
和FactoryAnnotation
。因此,到这里就知道之前ConfigurationBeanFactoryMetadata
的beansFactoryMetadata
集合存储工厂bean
元数据的作用了。
3.ConfigurationPropertiesBinder
分析this.configurationPropertiesBinder.bind(target);
这行代码:
1 | public void bind(Bindable<?> target) { |
上面代码的主要逻辑是:
- 先获取
target
对象(对应XxxProperties
类)上的@ConfigurationProperties
注解和校验器(若有); - 然后再根据获取的的
@ConfigurationProperties
注解和校验器来获得BindHandler
对象,BindHandler
的作用是用于在属性绑定时来处理一些附件逻辑; - 最后再获取一个
Binder
对象,调用其bind
方法来执行外部属性绑定的逻辑。
3.1 获取BindHandler对象以便在属性绑定时来处理一些附件逻辑
在看getBindHandler
方法的逻辑前先来认识下BindHandler
是干什么的。
BindHandler
是一个父类接口,用于在属性绑定时来处理一些附件逻辑。先看下BindHandler
的类图,好有一个整体的认识:
可以看到AbstractBindHandler
作为抽象基类实现了BindHandler
接口,其有四个具体的子类分别是IgnoreTopLevelConverterNotFoundBindHandler
,NoUnboundElementsBindHandler
,IgnoreErrorsBindHandler
和ValidationBindHandler
。
IgnoreTopLevelConverterNotFoundBindHandler
:在处理外部属性绑定时的默认BindHandler
,当属性绑定失败时会忽略最顶层的ConverterNotFoundException
;NoUnboundElementsBindHandler
:用来处理配置文件配置的未知的属性;IgnoreErrorsBindHandler
:用来忽略无效的配置属性例如类型错误;ValidationBindHandler
:利用校验器对绑定的结果值进行校验。
分析完类关系后,再来看下BindHandler
接口提供了哪些方法在外部属性绑定时提供一些额外的处理逻辑,直接看代码:
1 | public interface BindHandler { |
可以看到BindHandler
接口定义了onStart
,onSuccess
,onFailure
和onFinish
方法,这四个方法分别会在执行外部属性绑定时的不同时段被调用,在属性绑定时用来添加一些额外的处理逻辑,比如在onSuccess
方法改变最终绑定的属性值或对属性值进行校验,在onFailure
方法catch
住相关异常或者返回一个替代的绑定的属性值。
知道了BindHandler
是在属性绑定时添加一些额外的处理逻辑后,再来看下getBindHandler
方法的逻辑,直接上代码:
1 | // 注意BindHandler的设计技巧,应该是责任链模式,非常巧妙,值得借鉴 |
getBindHandler
方法的逻辑很简单,主要是根据传入的@ConfigurationProperties
注解和validators
校验器来创建不同的BindHandler
具体实现类:
- 首先
new
一个IgnoreTopLevelConverterNotFoundBindHandler
作为默认的BindHandler
; - 若
@ConfigurationProperties
注解的属性ignoreInvalidFields
值为true
,那么再new
一个IgnoreErrorsBindHandler
对象,把刚才新建的IgnoreTopLevelConverterNotFoundBindHandler
对象作为构造参数传入赋值给AbstractBindHandler
父类的parent
属性; - 若
@ConfigurationProperties
注解的属性ignoreUnknownFields
值为false
,那么再new
一个UnboundElementsSourceFilter
对象,把之前构造的BindHandler
对象作为构造参数传入赋值给AbstractBindHandler
父类的parent
属性; - ……以此类推,前一个
handler
对象作为后一个hangdler
对象的构造参数,就这样利用AbstractBindHandler
父类的parent
属性将每一个handler
链起来,最后再得到最终构造的handler
。
GET技巧:上面的这个设计模式是不是很熟悉,这个就是责任链模式。学习源码,同时也是学习别人怎么熟练运用设计模式。责任链模式的应用案例有很多,比如
Dubbo
的各种Filter
们(比如AccessLogFilter
是用来记录服务的访问日志的,ExceptionFilter
是用来处理异常的…),一开始学习java web时的Servlet
的Filter
,MyBatis
的Plugin
们以及Netty
的Pipeline
都采用了责任链模式。
了解了BindHandler
的作用后,再来紧跟主线,看属性绑定是如何绑定的?
3.2 获取Binder对象用于进行属性绑定【主线】
这里分析 ConfigurationPropertiesBinder代码中标注【4】
的主线代码getBinder().bind(annotation.prefix(), target, bindHandler);
可以看到这句代码主要做了两件事:
- 调用
getBinder
方法获取用于属性绑定的Binder
对象; - 调用
Binder
对象的bind
方法进行外部属性绑定到@ConfigurationProperties
注解的XxxProperties
类的属性上。
那么我们先看下getBinder
方法源码:
1 | private Binder getBinder() { |
可以看到Binder
对象封装了ConfigurationPropertySources
,PropertySourcesPlaceholdersResolver
,ConversionService
和PropertyEditorInitializer
这四个对象,Binder
对象封装了这四个肯定是在后面属性绑定逻辑中会用到,先看下这四个对象是干嘛的:
ConfigurationPropertySources
:外部配置文件的属性源,由ConfigFileApplicationListener
监听器负责触发读取;PropertySourcesPlaceholdersResolver
:解析属性源中的占位符${}
;ConversionService
:对属性类型进行转换PropertyEditorInitializer
:用来配置property editors
获取了Binder
属性绑定器后,再来看下它的bind
方法是如何执行属性绑定的。
1 | public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) { |
上面代码中首先创建了一个Context
对象,Context
是Binder
的内部类,为Binder
的上下文,利用Context
上下文可以获取Binder
的属性比如获取Binder
的sources
属性值并绑定到XxxProperties
属性中。然后再紧跟主线看下 bind(name, target, handler, context, false)
方法源码:
1 | protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { |
接着紧跟主线来看看bindObject
方法源码:
1 | private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { |
由上代码中可以看到bindObject
中执行属性绑定的逻辑会根据不同的属性类型进入不同的绑定逻辑中,例如:
application.properties
配置文件中配置了spring.profiles.active=dev
的话,那么将会进入return bindAggregate(name, target, handler, context, aggregateBinder);
这个属性绑定的代码逻辑;application.properties
配置文件中配置了server.port=9999
的话,那么将会进入return bindBean(name, target, handler, context, allowRecursiveBinding);
的属性绑定的逻辑。
再次紧跟主线,进入@ConfigurationProperties
注解的XxxProperties
类的属性绑定逻辑中的bindBean
方法中:
1 | // name指的是ConfigurationProperties的前缀名 |
从上面代码中,追根究底来到了外部配置属性绑定到XxxProperties
类属性中的比较底层的代码了,可以看到属性绑定的逻辑应该就在上面代码标注【主线】
的lambda
代码处了。这个属于SpringBoot的属性绑定Binder
的范畴,Binder
相关类是SpringBoot2.0才出现的,即对之前的属性绑定相关代码进行推翻重写了。
总结
好了,外部配置属性值是如何被绑定到XxxProperties
类属性上的源码分析就到此结束了,重要步骤总结:
- 首先是
@EnableConfigurationProperties
注解import
了EnableConfigurationPropertiesImportSelector
后置处理器; EnableConfigurationPropertiesImportSelector
后置处理器又向Spring
容器中注册了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
这两个bean
;- 其中
ConfigurationPropertiesBeanRegistrar
向Spring
容器中注册了XxxProperties
类型的bean
;ConfigurationPropertiesBindingPostProcessorRegistrar
向Spring
容器中注册了ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
两个后置处理器; ConfigurationBeanFactoryMetadata
后置处理器在初始化bean
factory
时将@Bean
注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;ConfigurationPropertiesBindingPostProcessor
后置处理器将外部配置属性值绑定到XxxProperties
类属性的逻辑委托给ConfigurationPropertiesBinder
对象,然后ConfigurationPropertiesBinder
对象又最终将属性绑定的逻辑委托给Binder
对象来完成。
可见,重要的是上面的第5步。
评论加载中