詳解@ConfigurationProperties實(shí)現(xiàn)原理與實(shí)戰(zhàn)
在SpringBoot中,當(dāng)需要獲取到配置文件數(shù)據(jù)時(shí),除了可以用Spring自帶的@Value注解外,SpringBoot提供了一種更加方便的方式:@ConfigurationProperties。只要在bean上添加上這個(gè)注解,指定好配置文件的前綴,那么對(duì)應(yīng)的配置文件數(shù)據(jù)就會(huì)自動(dòng)填充到bean中。舉個(gè)栗子,現(xiàn)在有如下配置:
myconfig.name=test myconfig.age=22 myconfig.desc=這是我的測(cè)試描述
添加對(duì)應(yīng)的配置類,并添加上注解@ConfigurationProperties,指定前綴為myconfig
@Component @ConfigurationProperties(prefix = "myconfig") public class MyConfig { private String name; private Integer age; private String desc; //get/set 略 @Override public String toString() { return "MyConfig [name=" + name + ", age=" + age + ", desc=" + desc + "]"; } }
添加使用:
public static void main(String[] args) throws Exception { SpringApplication springApplication = new SpringApplication(Application.class); // 非web環(huán)境 springApplication.setWebEnvironment(false); ConfigurableApplicationContext application = springApplication.run(args); MyConfig config = application.getBean(MyConfig.class); log.info(config.toString()); application.close(); }
可以看到輸出log
com.cml.chat.lesson.lesson3.Application - MyConfig [name=test, age=22, desc=這是我的測(cè)試描述]
對(duì)應(yīng)的屬性都注入了配置中的值,而且不需要其他操作。是不是非常神奇?那么下面來(lái)剖析下@ConfigurationProperties到底做了啥?
首先進(jìn)入@ConfigurationProperties源碼中,可以看到如下注釋提示:
See Also 中給我們推薦了ConfigurationPropertiesBindingPostProcessor,EnableConfigurationProperties兩個(gè)類,EnableConfigurationProperties先放到一邊,因?yàn)楹竺娴奈恼轮袝?huì)詳解EnableXX框架的實(shí)現(xiàn)原理,這里就先略過。那么重點(diǎn)來(lái)看看ConfigurationPropertiesBindingPostProcessor,光看類名是不是很親切?不知上篇文章中講的BeanPostProcessor還有印象沒,沒有的話趕緊回頭看看哦。
ConfigurationPropertiesBindingPostProcessor
一看就知道和BeanPostProcessor有扯不開的關(guān)系,進(jìn)入源碼可以看到,該類實(shí)現(xiàn)的BeanPostProcessor和其他多個(gè)接口:
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean, DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered
這里是不是非常直觀,光看類的繼承關(guān)系就可以猜出大概這個(gè)類做了什么。
BeanFactoryAware,EnvironmentAware,ApplicationContextAware是Spring提供的獲取Spring上下文中指定對(duì)象的方法而且優(yōu)先于BeanPostProcessor調(diào)用,至于如何工作的后面的文章會(huì)進(jìn)行詳解,這里只要先知道下作用就可以了。
此類同樣實(shí)現(xiàn)了InitializingBean接口,從上篇文章中已經(jīng)知道了InitializingBean是在BeanPostProcessor.postProcessBeforeInitialization之后調(diào)用,那么postProcessBeforeInitialization目前就是我們需要關(guān)注的重要入口方法。
先上源碼看看:
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //直接通過查找添加了ConfigurationProperties注解的的類 ConfigurationProperties annotation = AnnotationUtils .findAnnotation(bean.getClass(), ConfigurationProperties.class); if (annotation != null) { postProcessBeforeInitialization(bean, beanName, annotation); } //查找使用工廠bean中是否有ConfigurationProperties注解 annotation = this.beans.findFactoryAnnotation(beanName, ConfigurationProperties.class); if (annotation != null) { postProcessBeforeInitialization(bean, beanName, annotation); } return bean; } private void postProcessBeforeInitialization(Object bean, String beanName, ConfigurationProperties annotation) { Object target = bean; PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>( target); factory.setPropertySources(this.propertySources); factory.setValidator(determineValidator(bean)); // If no explicit conversion service is provided we add one so that (at least) // comma-separated arrays of convertibles can be bound automatically factory.setConversionService(this.conversionService == null ? getDefaultConversionService() : this.conversionService); if (annotation != null) { factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields()); factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields()); factory.setExceptionIfInvalid(annotation.exceptionIfInvalid()); factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties()); if (StringUtils.hasLength(annotation.prefix())) { factory.setTargetName(annotation.prefix()); } } try { factory.bindPropertiesToTarget(); } catch (Exception ex) { String targetClass = ClassUtils.getShortName(target.getClass()); throw new BeanCreationException(beanName, "Could not bind properties to " + targetClass + " (" + getAnnotationDetails(annotation) + ")", ex); } }
在postProcessBeforeInitialization方法中,會(huì)先去找所有添加了ConfigurationProperties注解的類對(duì)象,找到后調(diào)用postProcessBeforeInitialization進(jìn)行屬性數(shù)據(jù)裝配。
那么現(xiàn)在可以將實(shí)現(xiàn)拆分成如何尋找和如何裝配兩部分來(lái)說明,首先先看下如何查找到ConfigurationProperties注解類。
查找ConfigurationProperties
在postProcessBeforeInitialization方法中先通過AnnotationUtils查找類是否添加了@ConfigurationProperties注解,然后再通過 this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);繼續(xù)查找,下面詳解這兩步查找的作用。
AnnotationUtils
AnnotationUtils.findAnnotation(bean.getClass(),ConfigurationProperties.class);這個(gè)是Spring中常用的工具類了,通過反射的方式獲取類上的注解,如果此類添加了注解@ConfigurationProperties那么這個(gè)方法會(huì)返回這個(gè)注解對(duì)象和類上配置的注解屬性。
beans.findFactoryAnnotation
這里的beans是ConfigurationBeanFactoryMetaData對(duì)象。在Spring中,可以以工廠bean的方式添加bean,這個(gè)類的作用就是在工程bean中找到@ConfigurationProperties注解。下面分析下實(shí)現(xiàn)過程:
ConfigurationBeanFactoryMetaData
public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcessor { private ConfigurableListableBeanFactory beanFactory; private Map<String, MetaData> beans = new HashMap<String, MetaData>(); @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; //迭代所有的bean定義,找出那些是工廠bean的對(duì)象添加到beans中 for (String name : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(name); String method = definition.getFactoryMethodName(); String bean = definition.getFactoryBeanName(); if (method != null && bean != null) { this.beans.put(name, new MetaData(bean, method)); } } } public <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation( Class<A> type) { Map<String, Object> result = new HashMap<String, Object>(); for (String name : this.beans.keySet()) { if (findFactoryAnnotation(name, type) != null) { result.put(name, this.beanFactory.getBean(name)); } } return result; } public <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) { Method method = findFactoryMethod(beanName); return (method == null ? null : AnnotationUtils.findAnnotation(method, type)); } //略... private static class MetaData { private String bean; private String method; //構(gòu)造方法和其他方法略... } }
通過以上代碼可以得出ConfigurationBeanFactoryMetaData的工作機(jī)制,通過實(shí)現(xiàn)BeanFactoryPostProcessor,在回調(diào)方法postProcessBeanFactory中,查找出所有通過工廠bean實(shí)現(xiàn)的對(duì)象,并將其保存到beans map中,通過方法findFactoryAnnotation可以查詢到工廠bean中是否添加了對(duì)應(yīng)的注解。那么這里的功能就是查找工廠bean中有添加@ConfigurationProperties注解的類了。
屬性值注入
通過上述步驟,已經(jīng)確認(rèn)了當(dāng)前傳入的bean是否添加了@ConfigurationProperties注解。如果添加了則下一步就需要進(jìn)行屬性值注入了,核心代碼在方法postProcessBeforeInitialization中:
private void postProcessBeforeInitialization(Object bean, String beanName, ConfigurationProperties annotation) { Object target = bean; PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>( target); //重點(diǎn),這里設(shè)置數(shù)據(jù)來(lái)源 factory.setPropertySources(this.propertySources); factory.setValidator(determineValidator(bean)); //設(shè)置轉(zhuǎn)換器 factory.setConversionService(this.conversionService == null ? getDefaultConversionService() : this.conversionService); if (annotation != null) { //將annotation中配置的屬性配置到factory中 } try { //這里是核心,綁定屬性值到對(duì)象中 factory.bindPropertiesToTarget(); } catch (Exception ex) { //拋出異常 } }
繼續(xù)跟進(jìn)factory.bindPropertiesToTarget方法,在bindPropertiesToTarget方法中,調(diào)用的是doBindPropertiesToTarget方法:
private void doBindPropertiesToTarget() throws BindException { RelaxedDataBinder dataBinder //略... //1、獲取bean中所有的屬性名稱 Set<String> names = getNames(relaxedTargetNames); //2、將屬性名稱和前綴轉(zhuǎn)換為配置文件的key值 PropertyValues propertyValues = getPropertySourcesPropertyValues(names,relaxedTargetNames); //3、通過上面兩個(gè)步驟找到的屬性從配置文件中獲取數(shù)據(jù)通過反射注入到bean中 dataBinder.bind(propertyValues); //數(shù)據(jù)校驗(yàn) if (this.validator != null) { dataBinder.validate(); } //判斷數(shù)據(jù)綁定過程中是否有錯(cuò)誤 checkForBindingErrors(dataBinder); }
上面代碼中使用dataBinder.bind方法進(jìn)行屬性值賦值,源碼如下:
public void bind(PropertyValues pvs) { MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs); doBind(mpvs); } protected void doBind(MutablePropertyValues mpvs) { checkAllowedFields(mpvs); checkRequiredFields(mpvs); //進(jìn)行賦值 applyPropertyValues(mpvs); } protected void applyPropertyValues(MutablePropertyValues mpvs) { try { // Bind request parameters onto target object. getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); } catch (PropertyBatchUpdateException ex) { // Use bind error processor to create FieldErrors. for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) { getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult()); } } }
經(jīng)過以上步驟連續(xù)的方法調(diào)用后,最終調(diào)用的是ConfigurablePropertyAccessor.setPropertyValues使用反射進(jìn)行設(shè)置屬性值,到這里就不繼續(xù)深入了。想要繼續(xù)深入了解的可以繼續(xù)閱讀源碼,到最后可以發(fā)現(xiàn)調(diào)用的是AbstractNestablePropertyAccessor.processLocalProperty中使用反射進(jìn)行賦值。
上面的代碼分析非常清晰明了的解釋了如何查找@ConfigurationProperties對(duì)象和如何使用反射的方式進(jìn)行賦值。
總結(jié)
在上面的步驟中我們分析了@ConfigurationProperties從篩選bean到注入屬性值的過程,整個(gè)過程的難度還不算高,沒有什么特別的難點(diǎn),這又是一個(gè)非常好的BeanPostProcessor使用場(chǎng)景說明。
從本文中可以學(xué)習(xí)到BeanPostProcessor是在SpringBoot中運(yùn)用,以及如何通過AnnotationUtils與ConfigurationBeanFactoryMetaData結(jié)合對(duì)系統(tǒng)中所有添加了指定注解的bean進(jìn)行掃描。
到此這篇關(guān)于詳解@ConfigurationProperties實(shí)現(xiàn)原理與實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)@ConfigurationProperties原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot如何使用@ConfigurationProperties封裝配置文件
- @ConfigurationProperties遇到的坑及解決
- springboot @ConfigurationProperties和@PropertySource的區(qū)別
- SpringBoot @ConfigurationProperties注解的簡(jiǎn)單使用
- @ConfigurationProperties綁定配置信息至Array、List、Map、Bean的實(shí)現(xiàn)
- SpringBoot @ConfigurationProperties使用詳解
- Spring Boot2.0 @ConfigurationProperties使用詳解
- Java中@ConfigurationProperties實(shí)現(xiàn)自定義配置綁定問題分析
相關(guān)文章
SpringBoot實(shí)現(xiàn)過濾敏感詞的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用SpringBoot實(shí)現(xiàn)過濾敏感詞功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2022-08-08JAVA并發(fā)中VOLATILE關(guān)鍵字的神奇之處詳解
這篇文章主要給大家介紹了關(guān)于JAVA并發(fā)中VOLATILE關(guān)鍵字的神奇之處的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05五種單件模式之Singleton的實(shí)現(xiàn)方法詳解
本篇文章是對(duì)Singleton的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06SpringBoot前端傳遞數(shù)組后端接收兩種常用的方法
這篇文章主要給大家介紹了關(guān)于SpringBoot前端傳遞數(shù)組后端接收兩種常用的方法,文中通過代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-04-04