详解@ConfigurationProperties实现原理与实战
在SpringBoot中,当需要获取到配置⽂件数据时,除了可以⽤Spring⾃带的@Value注解外,SpringBoot提供了⼀种更加⽅便的⽅式:
@ConfigurationProperties。只要在bean上添加上这个注解,指定好配置⽂件的前缀,那么对应的配置⽂件数据就会⾃动填充到bean中。举个栗⼦,现在有如下配置:
myconfig.name=test
myconfig.age=22
myconfig.desc=这是我的测试描述
添加对应的配置类,并添加上注解@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环境
springApplication.setWebEnvironment(false);
ConfigurableApplicationContext application = springApplication.run(args);
MyConfig config = Bean(MyConfig.class);
log.String());
application.close();
}
可以看到输出log
对应的属性都注⼊了配置中的值,⽽且不需要其他操作。是不是⾮常神奇?那么下⾯来剖析下@ConfigurationProperties到底做了啥?
⾸先进⼊@ConfigurationProperties源码中,可以看到如下注释提⽰:
See Also 中给我们推荐了ConfigurationPropertiesBindingPostProcessor,EnableConfigurationProperties两个
类,EnableConfigurationProperties先放到⼀边,因为后⾯的⽂章中会详解EnableXX框架的实现原理,这⾥就先略过。那么重点来看看ConfigurationPropertiesBindingPostProcessor,光看类名是不是很亲切?不知上篇⽂章中讲的BeanPostProcessor还有印象没,没有的话赶紧回头看看哦。
ConfigurationPropertiesBindingPostProcessor
⼀看就知道和BeanPostProcessor有扯不开的关系,进⼊源码可以看到,该类实现的BeanPostProcessor和其他多个接⼝:
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean,
DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered
这⾥是不是⾮常直观,光看类的继承关系就可以猜出⼤概这个类做了什么。
BeanFactoryAware,EnvironmentAware,ApplicationContextAware是Spring提供的获取Spring上下⽂中指定对象的⽅法⽽且优先于BeanPostProcessor调⽤,⾄于如何⼯作的后⾯的⽂章会进⾏详解,这⾥只要先知道下作⽤就可以了。
此类同样实现了InitializingBean接⼝,从上篇⽂章中已经知道了InitializingBean是在BeanPostProcessor.postProcessBeforeInitialization 之后调⽤,那么postProcessBeforeInitialization⽬前就是我们需要关注的重要⼊⼝⽅法。
先上源码看看:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//直接通过查添加了ConfigurationProperties注解的的类
ConfigurationProperties annotation = AnnotationUtils
.Class(), 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.versionService == null
getDefaultConversionService() : versionService);
if (annotation != null) {
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
factory.ptionIfInvalid());
factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
if (StringUtils.hasLength(annotation.prefix())) {
factory.setTargetName(annotation.prefix());
}
}
try {
factory.bindPropertiesToTarget();
}
catch (Exception ex) {
String targetClass = Class());
throw new BeanCreationException(beanName, "Could not bind properties to "
+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
}
}
在postProcessBeforeInitialization⽅法中,会先去所有添加了ConfigurationProperties注解的类对象,到后调⽤postProcessBeforeInitialization进⾏属性数据装配。
那么现在可以将实现拆分成如何寻和如何装配两部分来说明,⾸先先看下如何查到ConfigurationProperties注解类。
查ConfigurationProperties
在postProcessBeforeInitialization⽅法中先通过AnnotationUtils查类是否添加了@ConfigurationProperties注解,然后再通过
this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);继续查,下⾯详解这两步查的作⽤。
AnnotationUtils
AnnotationUtils.Class(),ConfigurationProperties.class);这个是Spring中常⽤的⼯具类了,通过反射的⽅式获取类上的注解,如果此类添加了注解@ConfigurationProperties那么这个⽅法会返回这个注解对象和类上配置的注解属性。
beans.findFactoryAnnotation
这⾥的beans是ConfigurationBeanFactoryMetaData对象。在Spring中,可以以⼯⼚bean的⽅式添加bean,这个类的作⽤就
是在⼯程bean中到@ConfigurationProperties注解。下⾯分析下实现过程:
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的对象添加到beans中
for (String name : BeanDefinitionNames()) {
BeanDefinition definition = BeanDefinition(name);
String method = FactoryMethodName();
String bean = FactoryBeanName();
if (method != null && bean != null) {
this.beans.put(name, new MetaData(bean, method));
}
springboot原理图解}
}
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, Bean(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;
/
/构造⽅法和其他⽅法略...
}
}
通过以上代码可以得出ConfigurationBeanFactoryMetaData的⼯作机制,通过实现BeanFactoryPostProcessor,在回调⽅法postProcessBeanFactory中,查出所有通过⼯⼚bean实现的对象,并将其保存到beans map中,通过⽅法findFactoryAnnotation可以查询到⼯⼚bean中是否添加了对应的注解。那么这⾥的功能就是查⼯⼚bean中有添加@ConfigurationProperties注解的类了。
属性值注⼊
通过上述步骤,已经确认了当前传⼊的bean是否添加了@ConfigurationProperties注解。如果添加了则下⼀步就需要进⾏属性值注⼊了,核⼼代码在⽅法postProcessBeforeInitialization中:
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));
//设置转换器
factory.versionService == null
getDefaultConversionService() : versionService);
if (annotation != null) {
//将annotation中配置的属性配置到factory中
}
try {
//这⾥是核⼼,绑定属性值到对象中
factory.bindPropertiesToTarget();
}
catch (Exception ex) {
//抛出异常
}
}
继续跟进factory.bindPropertiesToTarget⽅法,在bindPropertiesToTarget⽅法中,调⽤的是doBindPropertiesToTarget⽅法:
private void doBindPropertiesToTarget() throws BindException {
RelaxedDataBinder dataBinder
//略...
//1、获取bean中所有的属性名称
Set<String> names = getNames(relaxedTargetNames);
//2、将属性名称和前缀转换为配置⽂件的key值
PropertyValues propertyValues = getPropertySourcesPropertyValues(names,relaxedTargetNames);
//3、通过上⾯两个步骤到的属性从配置⽂件中获取数据通过反射注⼊到bean中
dataBinder.bind(propertyValues);
//数据校验
if (this.validator != null) {
dataBinder.validate();
}
/
/判断数据绑定过程中是否有错误
checkForBindingErrors(dataBinder);
}
上⾯代码中使⽤dataBinder.bind⽅法进⾏属性值赋值,源码如下:
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);
//进⾏赋值
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());
}
}
}
经过以上步骤连续的⽅法调⽤后,最终调⽤的是ConfigurablePropertyAccessor.setPropertyValues使⽤反射进⾏设置属性值,到这⾥就不继续深⼊了。想要继续深⼊了解的可以继续阅读源码,到最后可以发现调⽤的是
AbstractNestablePropertyAccessor.processLocalProperty中使⽤反射进⾏赋值。
上⾯的代码分析⾮常清晰明了的解释了如何查@ConfigurationProperties对象和如何使⽤反射的⽅式进⾏赋值。
总结
在上⾯的步骤中我们分析了@ConfigurationProperties从筛选bean到注⼊属性值的过程,整个过程的难度还不算⾼,没有什么特别的难点,这⼜是⼀个⾮常好的BeanPostProcessor使⽤场景说明。
从本⽂中可以学习到BeanPostProcessor是在SpringBoot中运⽤,以及如何通过AnnotationUtils与ConfigurationBeanFactoryMetaData结合对系统中所有添加了指定注解的bean进⾏扫描。
到此这篇关于详解@ConfigurationProperties实现原理与实战的⽂章就介绍到这了,更多相关@ConfigurationProperties原理内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。