1 @EnableConfigurationProperties
我们接着前面的设置服务器端口的栗子来分析,我们先直接来看看ServerProperties的源码,应该能找到源码的入口:
1 | (prefix = "server", ignoreUnknownFields = true) |
可以看到,ServerProperties类上标注了@ConfigurationProperties这个注解,服务器属性配置前缀为server,是否忽略未知的配置值(ignoreUnknownFields)设置为true。
那么我们再来看下@ConfigurationProperties这个注解的源码:
1 | ({ ElementType.TYPE, ElementType.METHOD }) |
@ConfigurationProperties这个注解的作用就是将外部配置的配置值绑定到其注解的类的属性上,可以作用于配置类或配置类的方法上。可以看到@ConfigurationProperties注解除了有设置前缀,是否忽略一些不存在或无效的配置等属性等外,这个注解没有其他任何的处理逻辑,可以看到@ConfigurationProperties是一个标志性的注解,源码入口不在这里。
这里讲的是服务器的自动配置,自然而然的,我们来看下自动配置类ServletWebServerFactoryAutoConfiguration的源码:
1 |
|
为了突出重点,我已经把ServletWebServerFactoryAutoConfiguration的非关键代码和非关键注解省略掉了。可以看到,ServletWebServerFactoryAutoConfiguration自动配置类中有一个@EnableConfigurationProperties注解,且注解值是前面讲的ServerProperties.class,因此@EnableConfigurationProperties注解肯定就是我们关注的重点了。
同样,再来看下@EnableConfigurationProperties注解的源码:
1 | (ElementType.TYPE) |
@EnableConfigurationProperties注解的主要作用就是为@ConfigurationProperties注解标注的类提供支持,即对将外部配置属性值(比如application.properties配置值)绑定到@ConfigurationProperties标注的类的属性中。
注意:SpringBoot源码中还存在了
ConfigurationPropertiesAutoConfiguration这个自动配置类,同时spring.factories配置文件中的EnableAutoConfiguration接口也配置了ConfigurationPropertiesAutoConfiguration,这个自动配置类上也有@EnableConfigurationProperties这个注解,堆属性绑定进行了默认开启。
那么,@EnableConfigurationProperties这个注解对属性绑定提供怎样的支持呢?
可以看到@EnableConfigurationProperties这个注解上还标注了@Import(EnableConfigurationPropertiesImportSelector.class),其导入了EnableConfigurationPropertiesImportSelector,因此可以肯定的是@EnableConfigurationProperties这个注解对属性绑定提供的支持必定跟EnableConfigurationPropertiesImportSelector有关。
到了这里,EnableConfigurationPropertiesImportSelector这个哥们是我们接下来要分析的对象,那么我们下面继续来分析EnableConfigurationPropertiesImportSelector是如何承担将外部配置属性值绑定到@ConfigurationProperties标注的类的属性中的。
2 EnableConfigurationPropertiesImportSelector
EnableConfigurationPropertiesImportSelector类的作用主要用来处理外部属性绑定的相关逻辑,其实现了ImportSelector接口,我们都知道,实现ImportSelector接口的selectImports方法可以向容器中注册bean。
那么,我们来看下EnableConfigurationPropertiesImportSelector覆写的selectImports方法:
1 | // EnableConfigurationPropertiesImportSelector.java |
可以看到EnableConfigurationPropertiesImportSelector类中的selectImports方法中返回的是IMPORTS数组,而这个IMPORTS是一个常量数组,值是ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar。即EnableConfigurationPropertiesImportSelector的作用是向Spring容器中注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean。
我们在EnableConfigurationPropertiesImportSelector类中没看到处理外部属性绑定的相关逻辑,其只是注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean,接下来我们再看下注册的这两个bean类。
3 ConfigurationPropertiesBeanRegistrar
我们先来看下ConfigurationPropertiesBeanRegistrar这个类。
ConfigurationPropertiesBeanRegistrar是EnableConfigurationPropertiesImportSelector的内部类,其实现了ImportBeanDefinitionRegistrar接口,覆写了registerBeanDefinitions方法。可见,ConfigurationPropertiesBeanRegistrar又是用来注册一些bean definition的,即也是向Spring容器中注册一些bean。
先看下ConfigurationPropertiesBeanRegistrar的源码:
1 | // ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java |
在ConfigurationPropertiesBeanRegistrar实现的registerBeanDefinitions中,可以看到主要做了两件事:
- 调用
getTypes方法获取@EnableConfigurationProperties注解的属性值XxxProperties; - 调用
register方法将获取的属性值XxxProperties注册到Spring容器中,用于以后和外部属性绑定时使用。
我们来看下getTypes方法的源码:
1 | // ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java |
getTypes方法里面的逻辑很简单即将@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)取出并装进List集合并返回。
由getTypes方法拿到@EnableConfigurationProperties注解里面的属性值XxxProperties(比如ServerProperties.class)后,此时再遍历将XxxProperties逐个注册进Spring容器中,我们来看下register方法:
1 | // ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java |
我们再来看下由EnableConfigurationPropertiesImportSelector导入的另一个类ConfigurationPropertiesBindingPostProcessorRegistrar又是干嘛的呢?
4 ConfigurationPropertiesBindingPostProcessorRegistrar
可以看到ConfigurationPropertiesBindingPostProcessorRegistrar类名字又是以Registrar单词为结尾,说明其肯定又是导入一些bean definition的。直接看源码:
1 | // ConfigurationPropertiesBindingPostProcessorRegistrar.java |
ConfigurationPropertiesBindingPostProcessorRegistrar类的逻辑非常简单,主要用来注册外部配置属性绑定相关的后置处理器即ConfigurationBeanFactoryMetadata和ConfigurationPropertiesBindingPostProcessor。
那么接下来我们再来探究下注册的这两个后置处理器又是执行怎样的后置处理逻辑呢?
5 ConfigurationBeanFactoryMetadata
先来看ConfigurationBeanFactoryMetadata这个后置处理器,其实现了BeanFactoryPostProcessor接口的postProcessBeanFactory方法,在初始化bean factory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用。
先来看下ConfigurationBeanFactoryMetadata类实现BeanFactoryPostProcessor接口的postProcessBeanFactory方法源码:
1 | // ConfigurationBeanFactoryMetadata |
从上面代码可以看到ConfigurationBeanFactoryMetadata类覆写的postProcessBeanFactory方法做的事情就是将工厂Bean(可以理解为@Configuration注解的类)及其@Bean注解的工厂方法的一些元数据缓存到beansFactoryMetadata集合中,以便后续使用,这个后面会详述。
由上代码中我们看到了ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合类型是Map<String, FactoryMetadata>,那么我们再来看下封装相关工厂元数据的FactoryMetadata类:
1 | // ConfigurationBeanFactoryMetadata$FactoryMetadata.java |
FactoryMetadata仅有两个属性bean和method,分别表示@Configuration注解的工厂bean和@Bean注解的工厂方法。
上面说了那么多,直接举个栗子会更直观:
1 | /** |
为了更好理解上面beansFactoryMetadata集合存储的数据是啥,建议最好自己动手调试看看其里面装的是什么哦。总之这里记住一点就好了:ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合存储的是工厂bean的相关元数据,以便在ConfigurationPropertiesBindingPostProcessor后置处理器中使用。
6 ConfigurationPropertiesBindingPostProcessor
我们再来看下ConfigurationPropertiesBindingPostProcessorRegistrar类注册的另外一个后置处理器ConfigurationPropertiesBindingPostProcessor,这个后置处理器就尤其重要了,主要承担了将外部配置属性绑定到@ConfigurationProperties注解标注的XxxProperties类的属性中(比如application.properties配置文件中设置了server.port=8081,那么8081将会绑定到ServerProperties类的port属性中)的实现逻辑。
同样,先来看下ConfigurationPropertiesBindingPostProcessor的源码:
1 | // ConfigurationPropertiesBindingPostProcessor.java |
可以看到ConfigurationPropertiesBindingPostProcessor后置处理器实现了两个重要的接口InitializingBean和BeanPostProcessor。
我们都知道:
InitializingBean接口的afterPropertiesSet方法会在bean属性赋值后调用,用来执行一些自定义的初始化逻辑比如检查某些强制的属性是否有被赋值,校验某些配置或给一些未被赋值的属性赋值。BeanPostProcessor接口是bean的后置处理器,其有postProcessBeforeInitialization和postProcessAfterInitialization两个勾子方法,分别会在bean初始化前后被调用来执行一些后置处理逻辑,比如检查标记接口或是否用代理包装了bean。
同时由上代码可以看到ConfigurationPropertiesBindingPostProcessor后置处理器覆写了InitializingBean的afterPropertiesSet方法和BeanPostProcessor的postProcessBeforeInitialization方法。
接下来我们再来探究ConfigurationPropertiesBindingPostProcessor后置处理器覆写的两个方法的源码。
6.1 在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器
我们先来分析下ConfigurationPropertiesBindingPostProcessor覆写InitializingBean接口的afterPropertiesSet方法:
1 | // ConfigurationPropertiesBindingPostProcessor.java |
可以看到以上代码主要逻辑就是在执行外部属性绑定逻辑前先准备好相关元数据和配置属性绑定器,即从Spring容器中获取到之前注册的ConfigurationBeanFactoryMetadata对象赋给ConfigurationPropertiesBindingPostProcessor后置处理器的beanFactoryMetadata属性,还有就是新建一个ConfigurationPropertiesBinder配置属性绑定器对象并赋值给configurationPropertiesBinder属性。
我们再来看下ConfigurationPropertiesBinder这个配置属性绑定器对象是如何构造的。
1 | // ConfigurationPropertiesBinder.java |
可以看到在构造ConfigurationPropertiesBinder对象时主要给其相关属性赋值(一般构造器逻辑都是这样):
- 给
applicationContext属性赋值注入上下文对象; - 给
propertySources属性赋值,属性源即外部配置值比如application.properties配置的属性值,注意这里的属性源是由ConfigFileApplicationListener这个监听器负责读取的,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。
6.2 执行真正的外部属性绑定逻辑【主线】
前面分析了那么多,发现都还没到外部属性绑定的真正处理逻辑,前面步骤都是在做一些准备性工作,为外部属性绑定做铺垫。
在执行外部属性绑定逻辑前,准备好了相关元数据和配置属性绑定器后,此时我们再来看看ConfigurationPropertiesBindingPostProcessor实现BeanPostProcessor接口的postProcessBeforeInitialization后置处理方法了,外部属性绑定逻辑都是在这个后置处理方法里实现,是我们关注的重中之重。
直接看代码:
1 | // ConfigurationPropertiesBindingPostProcessor.java |
ConfigurationPropertiesBindingPostProcessor类覆写的postProcessBeforeInitialization方法的做的事情就是将外部属性配置绑定到@ConfigurationProperties注解标注的XxxProperties类上,现关键步骤总结如下:
- 从
bean上获取@ConfigurationProperties注解; - 若标注有
@ConfigurationProperties注解的bean,那么则进行进一步的处理:将外部配置属性值绑定到bean的属性值中后再返回bean;若没有标注有@ConfigurationProperties注解的bean,那么将直接原样返回bean。
注意:后置处理器默认会对每个容器中的
bean进行后置处理,因为这里只针对标注有@ConfigurationProperties注解的bean进行外部属性绑定,因此没有标注@ConfigurationProperties注解的bean将不会被处理。
接下来我们紧跟主线,再来看下外部配置属性是如何绑定到@ConfigurationProperties注解的XxxProperties类属性上的呢?
直接看代码:
1 | // ConfigurationPropertiesBindingPostProcessor.java |
关键步骤上面代码已经标注【x】,这里在继续讲解外部配置属性绑定的主线逻辑(在8 ConfigurationPropertiesBinder这一小节分析 )前先穿插一个知识点,还记得ConfigurationBeanFactoryMetadata覆写的postProcessBeanFactory方法里已经将相关工厂bean的元数据封装到ConfigurationBeanFactoryMetadata类的beansFactoryMetadata集合这一回事吗?
我们再来看下上面代码中的【1】getBeanType和【2】getAnnotation方法源码:
1 | // ConfigurationPropertiesBindingPostProcessor.java |
注意到上面代码中的beanFactoryMetadata对象没,ConfigurationPropertiesBindingPostProcessor后置处理器的getBeanType和getAnnotation方法分别会调用ConfigurationBeanFactoryMetadata的findFactoryMethod和findFactoryAnnotation方法,而ConfigurationBeanFactoryMetadata的findFactoryMethod和findFactoryAnnotation方法又会依赖存储工厂bean元数据的beansFactoryMetadata集合来寻找是否有FactoryMethod和FactoryAnnotation。因此,到这里我们就知道之ConfigurationBeanFactoryMetadata的beansFactoryMetadata集合存储工厂bean元数据的作用了。
7 ConfigurationPropertiesBinder
我们再继续紧跟外部配置属性绑定的主线,继续前面看7.2 执行真正的外部属性绑定逻辑中的this.configurationPropertiesBinder.bind(target);这句代码:
1 | // ConfigurationPropertiesBinder.java |
上面代码的主要逻辑是:
- 先获取
target对象(对应XxxProperties类)上的@ConfigurationProperties注解和校验器(若有); - 然后再根据获取的的
@ConfigurationProperties注解和校验器来获得BindHandler对象,BindHandler的作用是用于在属性绑定时来处理一些附件逻辑;在7.1节分析. - 最后再获取一个
Binder对象,调用其bind方法来执行外部属性绑定的逻辑,在7.2节分析.
7.1 获取BindHandler对象以便在属性绑定时来处理一些附件逻辑
我们在看getBindHandler方法的逻辑前先来认识下BindHandler是干啥的。
BindHandler是一个父类接口,用于在属性绑定时来处理一些附件逻辑。我们先看下BindHandler的类图,好有一个整体的认识:

图1
可以看到AbstractBindHandler作为抽象基类实现了BindHandler接口,其又有四个具体的子类分别是IgnoreTopLevelConverterNotFoundBindHandler,NoUnboundElementsBindHandler,IgnoreErrorsBindHandler和ValidationBindHandler。
IgnoreTopLevelConverterNotFoundBindHandler:在处理外部属性绑定时的默认BindHandler,当属性绑定失败时会忽略最顶层的ConverterNotFoundException;NoUnboundElementsBindHandler:用来处理配置文件配置的未知的属性;IgnoreErrorsBindHandler:用来忽略无效的配置属性例如类型错误;ValidationBindHandler:利用校验器对绑定的结果值进行校验。
分析完类关系后,我们再来看下BindHandler接口提供了哪些方法在外部属性绑定时提供一些额外的附件逻辑,直接看代码:
1 | // BindHandler.java |
可以看到BindHandler接口定义了onStart,onSuccess,onFailure和onFinish方法,这四个方法分别会在执行外部属性绑定时的不同时机会被调用,在属性绑定时用来添加一些额外的处理逻辑,比如在onSuccess方法改变最终绑定的属性值或对属性值进行校验,在onFailure方法catch住相关异常或者返回一个替代的绑定的属性值。
知道了BindHandler是在属性绑定时添加一些额外的附件处理逻辑后,我们再来看下getBindHandler方法的逻辑,直接上代码:
1 | // ConfigurationPropertiesBinder.java |
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的作用后,再来紧跟主线,看属性绑定是如何绑定的?
7.2 获取Binder对象用于进行属性绑定【主线】
这里接7 ConfigurationPropertiesBinder节代码中标注【4】的主线代码getBinder().bind(annotation.prefix(), target, bindHandler);.
可以看到这句代码主要做了两件事:
- 调用
getBinder方法获取用于属性绑定的Binder对象; - 调用
Binder对象的bind方法进行外部属性绑定到@ConfigurationProperties注解的XxxProperties类的属性上。
那么我们先看下getBinder方法源码:
1 | // ConfigurationPropertiesBinder.java |
可以看到Binder对象封装了ConfigurationPropertySources,PropertySourcesPlaceholdersResolver,ConversionService和PropertyEditorInitializer这四个对象,Binder对象封装了这四个哥们肯定是在后面属性绑定逻辑中会用到,先看下这四个对象是干嘛的:
ConfigurationPropertySources:外部配置文件的属性源,由ConfigFileApplicationListener监听器负责触发读取;PropertySourcesPlaceholdersResolver:解析属性源中的占位符${};ConversionService:对属性类型进行转换PropertyEditorInitializer:用来配置property editors
那么,我们获取了Binder属性绑定器后,再来看下它的bind方法是如何执行属性绑定的。
1 | // Binder.java |
上面代码中首先创建了一个Context对象,Context是Binder的内部类,为Binder的上下文,利用Context上下文可以获取Binder的属性比如获取Binder的sources属性值并绑定到XxxProperties属性中。然后我们再紧跟主线看下bind(name, target, handler, context, false)方法源码:
1 | // Binder.java |
上面代码的注释已经非常详细,这里不再详述。我们接着紧跟主线来看看bindObject方法源码:
1 | // Binder.java |
由上代码中可以看到bindObject中执行属性绑定的逻辑会根据不同的属性类型进入不同的绑定逻辑中,举个栗子:
application.properties配置文件中配置了spring.profiles.active=dev的话,那么将会进入return bindAggregate(name, target, handler, context, aggregateBinder);这个属性绑定的代码逻辑;application.properties配置文件中配置了server.port=8081的话,那么将会进入return bindBean(name, target, handler, context, allowRecursiveBinding);的属性绑定的逻辑。
因此我们再次紧跟主线,进入@ConfigurationProperties注解的XxxProperties类的属性绑定逻辑中的bindBean方法中:
1 | // Binder.java |
从上面代码中,我们追根究底来到了外部配置属性绑定到XxxProperties类属性中的比较底层的代码了,可以看到属性绑定的逻辑应该就在上面代码标注【主线】的lambda代码处了。这里就不再详述了,因为这个属于SpringBoot的属性绑定Binder的范畴,Binder相关类是SpringBoot2.0才出现的,即对之前的属性绑定相关代码进行推翻重写了。属性绑定相关的源码也比较多,后续有需要再另开一篇来分析探究吧。
9 小结
好了,外部配置属性值是如何被绑定到XxxProperties类属性上的源码分析就到此结束了,又是蛮长的一篇文章,不知自己表述清楚没,重要步骤现总结下:
- 首先是
@EnableConfigurationProperties注解import了EnableConfigurationPropertiesImportSelector后置处理器; EnableConfigurationPropertiesImportSelector后置处理器又向Spring容器中注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean;- 其中
ConfigurationPropertiesBeanRegistrar向Spring容器中注册了XxxProperties类型的bean;ConfigurationPropertiesBindingPostProcessorRegistrar向Spring容器中注册了ConfigurationBeanFactoryMetadata和ConfigurationPropertiesBindingPostProcessor两个后置处理器; ConfigurationBeanFactoryMetadata后置处理器在初始化beanfactory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;ConfigurationPropertiesBindingPostProcessor后置处理器将外部配置属性值绑定到XxxProperties类属性的逻辑委托给ConfigurationPropertiesBinder对象,然后ConfigurationPropertiesBinder对象又最终将属性绑定的逻辑委托给Binder对象来完成。
可见,重要的是上面的第5步