详解@ConfigurationProperties实现原理与实战
作者:cmlbeliever 发布时间:2023-11-24 05:19:26
在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 = application.getBean(MyConfig.class);
log.info(config.toString());
application.close();
}
可以看到输出log
com.cml.chat.lesson.lesson3.Application - MyConfig [name=test, age=22, desc=这是我的测试描述]
对应的属性都注入了配置中的值,而且不需要其他操作。是不是非常神奇?那么下面来剖析下@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
.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方法中,会先去找所有添加了ConfigurationProperties注解的类对象,找到后调用postProcessBeforeInitialization进行属性数据装配。
那么现在可以将实现拆分成如何寻找和如何装配两部分来说明,首先先看下如何查找到ConfigurationProperties注解类。
查找ConfigurationProperties
在postProcessBeforeInitialization方法中先通过AnnotationUtils查找类是否添加了@ConfigurationProperties注解,然后再通过 this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);继续查找,下面详解这两步查找的作用。
AnnotationUtils
AnnotationUtils.findAnnotation(bean.getClass(),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 : 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;
//构造方法和其他方法略...
}
}
通过以上代码可以得出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.setConversionService(this.conversionService == null
? getDefaultConversionService() : this.conversionService);
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进行扫描。
来源:https://blog.csdn.net/cml_blog/article/details/102234714
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 开发语言:C#3.0 IDE:Visual Studio 2008 一、C#线程概述 在操作系统中一个进程至少要包含一个线程,然后,在某些时
- 我在 android里面 使用html5的 localStorage 为什么存不进去也读不出来呀?网上搜了好多都没效果mainWebView
- 本文实例为大家分享了DatePicker日期滚动选择的使用,供大家参考,具体内容如下效果图为:1.dialog_date.xml:<?
- 本文实例讲述了Android编程实现图片的浏览、缩放、拖动和自动居中效果的方法。分享给大家供大家参考,具体如下:Touch.java/**
- 链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。链表在数据结构中是基础
- 由于一个线程的程序,如果调用一个功能是阻塞的,那么就会影响到界面的更新,导致使用人员操作不便。所以往往会引入双线程的工作的方式,主线程负责更
- 1.介绍当系统准备为用户提供一系列相关对象,又不想让用户代码和这些对象形成耦合时,就可以使用抽象工厂模式。2.如何实现1)抽象产品--Car
- xxljob介绍XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上
- 今天深度学习一下《Java并发编程的艺术》的第1章并发编程的挑战,深入理解Java多线程,看看多线程中的坑。注意,哈肯的程序员读书笔记并不是
- 本文分享了如何对Dubbo服务进行优雅的参数校验,以实现服务端统一的数据返回格式,同时也在一定程度提升开发效率,避免重复简单的参数校验逻辑.
- 接收到这样一个需求,就是英文名字中firstName和lastName,其中任何一个为null,就返回Empty。刚拿到需求,这不简单,if
- VisualVM是JDK自带的一款全能型性能监控和故障分析工具,包括对CPU使用、JVM堆内存消耗、线程、类加载的实时监控,内存dump文件
- 1、抽象类1.1 什么是抽象类?1.1.1 对抽象类的理解1.1.2 关于抽象类类与类之间具有共同特征,将这些共同特征提取出来,形成的就是抽
- 先给大家简单介绍下mybatisMyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的J
- 开发环境: IDEA 2022.1.41. 概述虽然webservice这块使用很少,但在局域网作服务还是相当不错。今天突生想法,想做一个来
- 最近要给一个 Winform 项目添加功能,需要一个能显示进度条的弹窗,还要求能够中止任务,所以就做了一个,在此做个记录总结。虽然用的是比较
- 目录Jacoco原理简介使用Jacoco生成代码执行覆盖率报告小结Jacoco是Java Code Coverage的缩写,顾名思义,它是获
- 本文实例讲述了Android编程实现添加低电流提醒功能的方法。分享给大家供大家参考,具体如下:特殊需求,检测电流是否正常。监听如下广播:In
- 本文实例为大家分享了UnityShader3实现彩光效果展示的具体代码,供大家参考,具体内容如下参考链接: 【OpenGL】Shader实例
- 关于java8 的stream排序用法这里不做多说,这里介绍下曾经在多字段排序时遇到过的一个坑。需求:需要根据id去分组,然后取出每组中行号