1.简介

1.1 概述

Annotation for externalized configuration. Add this to a class definition or a @Bean method in a @Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).

Binding is either performed by calling setters on the annotated class or, if @ConstructorBinding is in use, by binding to the constructor parameters.

Note that contrary to @Value, SpEL expressions are not evaluated since property values are externalized.

一个外部化配置的注解。如果您要绑定和验证某些外部属性(例如,来自.properties文件),则将其添加到类定义或 @Configuration 类中的 @Bean 方法中。

绑定可以通过在带注释的类上调用setter来执行,或者,如果正在使用 @ConstructorBinding,则可以通过绑定到构造函数参数来执行。

请注意,与@Value相反,由于属性值是外部化的,因此不评估SpEL表达式。

1.2 特点

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {String value() default "";String prefix() default "";boolean ignoreInvalidFields() default false;boolean ignoreUnknownFields() default true;
}

1.3 对比 @Value

@Configuration @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SPEL语法 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持

2.环境

  1. JDK 1.8.0_201
  2. Spring Boot 2.2.0.RELEASE
  3. 构建工具(apache maven 3.6.3)
  4. 开发工具(IntelliJ IDEA )

3.代码

3.1 代码结构

3.2 maven 依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

3.3 配置文件

application.properties

user.prop.name=zhangsan
user.prop.age=20

3.4 java代码

UserProperties.java

@Component
@Validated // JSR303数据校验
@ConfigurationProperties(prefix = "user.prop")
public class UserProperties {@NotBlankprivate String name;@Range(min = 1, max = 200)private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

UserProps.java

@Component
public class UserProps {@Value("${user.prop.name}")private String name;// SPEL 表达式@Value("#{10 * 2}")private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

UserController.java

@RestController
public class UserController {@Autowiredprivate UserProperties userProperties;@Autowiredprivate UserProps userProps;@GetMapping("/user/get/1")public String getUser1() {return userProperties.getName() + "'s age is " + userProperties.getAge();}@GetMapping("/user/get/2")public String getUser2() {return userProps.getName() + "'s age is " + userProps.getAge();}
}

3.5 git 地址

spring-boot/spring-boot-02-config

4.结果

启动 SpringBoot02ConfigApplication.main 方法,在 spring-boot-02-config.http 访问如下两个地址,输出 “zhangsan’s age is 20” 表示请求成功

5.源码分析

5.1 @ConfigurationProperties 原理分析

@SpringBootApplication 注解是一个复合注解,它里面包含一个 @ConfigurationPropertiesScan,这个里面又有一个 @EnableConfigurationProperties,@ConfigurationProperties 的作用与它有关。

@ConfigurationProperties 中通过 @Import 引入一个 EnableConfigurationPropertiesRegistrar,它里面有一个 registerBeanDefinitions 方法

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {registerInfrastructureBeans(registry);ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);getTypes(metadata).forEach(beanRegistrar::register);
}

registerBeanDefinitions 调用一个 registerInfrastructureBeans ,这个方法将 属性绑定后置处理器、bean 校验器、元数据注入到 registry 中,这里的 registry 保存了所有 bean 信息。

static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {ConfigurationPropertiesBindingPostProcessor.register(registry);ConfigurationPropertiesBeanDefinitionValidator.register(registry);ConfigurationBeanFactoryMetadata.register(registry);
}

通过查看类图可以知道,ConfigurationPropertiesBindingPostProcessor 是 BeanPostProcessor 的一个实现类

它在 bean 实例化的时候发生作用,BeanPostProcessor 提供了 postProcessBeforeInitialization 和

postProcessAfterInitialization 两个方法

public interface BeanPostProcessor {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}
}

在 ConfigurationPropertiesBindingPostProcessor 的 postProcessBeforeInitialization 方法中提供了对于属性值的注入

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 属性绑定bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));return bean;
}

在 bind 方法中,通过 ConfigurationPropertiesBinder 来绑定 ConfigurationProperties 中属性

BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {Bindable<?> target = propertiesBean.asBindTarget();// 获取目标 bean 上的 @ConfigurationProperties 注解ConfigurationProperties annotation = propertiesBean.getAnnotation();// 获取 BindHandlerBindHandler bindHandler = getBindHandler(target, annotation);// 通过配置的 prefix 和 BindHandler 进行属性绑定return getBinder().bind(annotation.prefix(), target, bindHandler);
}

到这里已经比较清晰了,后面的就是从 应用上下文中获取属性值,然后转换成对应的类型,再将属性值设置给目标对象。

5.2 @Value 原理分析

这个流程中,doCreateBean 前面的流程实际上是 spirng bean 的初始化流程,在初始化过程中,会对 bean 的依赖和字段进行填充;BeanPostProcessor 也是在这个阶段发生作用

for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}
}

使用注解进行 bean 注入的时候,会有一个 AutowiredAnnotationBeanPostProcessor 的处理类,它里面有一个 postProcessProperties 方法

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}

InjectionMetadata 是类的注入元数据,这里通过它来对 bean 中的属性进行注入,它里面提供了多种注入元件,而 ConfigurationProperties 主要通过字段属性进行注入

AutowiredFieldElement 的 inject 方法实现如下

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;// 判断是否已缓存,如果缓存了,直接获取if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {// 如果没有缓存,需要从 beanFactory 中获取具体值,然后缓存起来DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try {value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 将获取到的值缓存起来this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}else {this.cachedFieldValue = null;}// 修改标记this.cached = true;}}}if (value != null) {// 最终将获取到的值,通过反射进行注入ReflectionUtils.makeAccessible(field);field.set(bean, value);}
}

接下来调用流程是 resolveDependency -> doResolveDependency -> resolveEmbeddedValue

@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {if (value == null) {return null;}String result = value;for (StringValueResolver resolver : this.embeddedValueResolvers) {result = resolver.resolveStringValue(result);if (result == null) {return null;}}return result;
}

最后调用到 PropertyPlaceholderConfigurer,通过解析配置文件获取到最终值

@Override
@Nullable
public String resolveStringValue(String strVal) throws BeansException {String resolved = this.helper.replacePlaceholders(strVal, this.resolver);if (trimValues) {resolved = resolved.trim();}return (resolved.equals(nullValue) ? null : resolved);
}

6.参考

  1. @ConfigurationProperties与@Value的区别
  2. springboot中@Value的工作原理

2.SpringBoot学习(二)——Spring Boot ConfigurationProperties相关推荐

  1. spring boot 学习(二)spring boot 框架整合 thymeleaf

    spring boot 框架整合 thymeleaf spring boot 的官方文档中建议开发者使用模板引擎,避免使用 JSP.因为若一定要使用 JSP 将无法使用. 注意:本文主要参考学习了大神 ...

  2. SpringBoot开发之Spring Boot入门

    SpringBoot开发之SpringBoot入门 一.Spring Boot概述 1.什么是Spring Boot 2.Spring Boot的优点 二.第一个Spring Boot应用 1.创建S ...

  3. Spring Boot(四)Spring Boot @ConfigurationProperties实例

    Spring Boot @ConfigurationProperties实例 一 . ConfigurationProperties的使用 通常,我们使用@Value注释来逐个注入.propertie ...

  4. springboot自动配置原理_今日份学习之Spring Boot自动配置实现原理

    通过前面章节的学习,我们掌握了使用Spring Boot框架进行实际应用开发的方法.在使用Spring Boot 的过程中,我们时常会为一些看似简单,但实际上蕴藏了强大功能的实现而惊呼,下面就让我们来 ...

  5. (附源码)springboot+mysql+基于Spring boot开发电子宿舍管理系统 毕业设计132056

    摘 要 科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用.信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代. ...

  6. springboot+mysql+基于Spring boot开发电子宿舍管理系统 毕业设计-附源码132056

    摘  要 科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用.信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代 ...

  7. SpringBoot (八) :Spring Boot多数据源(JdbcTemplate)配置与使用

    什么是JdbcTemplate 为了使 JDBC 更加易于使用,Spring 在 JDBCAPI 上定义了一个抽象层, 以此建立一个JDBC存取框架. 作为 SpringJDBC 框架的核心, JDB ...

  8. Elasticsearch学习(3) spring boot整合Elasticsearch的原生方式

    前面我们已经介绍了spring boot整合Elasticsearch的jpa方式,这种方式虽然简便,但是依旧无法解决我们较为复杂的业务,所以原生的实现方式学习能够解决这些问题,而原生的学习方式也是E ...

  9. Spring Boot——@ConfigurationProperties与@Value的区别

    引言 Spring Boot从配置文件中取值的方式有两种,一种是批量注入@ConfigurationProperties,另一种是单独注入@Value. 它们之间除了批量与单独取值的区别之外,还存在着 ...

最新文章

  1. oracle备份镜像,Oracle RMAN两种备份方式 – 备份集备份与镜像复制备份
  2. when is odata request sent for Live report in SAP CRM
  3. 信息学奥赛一本通 1311:【例2.5】求逆序对 | 1237:求排列的逆序数 | OpenJudge NOI 2.4 7622:求排列的逆序数 | 洛谷 P1908 逆序对
  4. vue-cli搭建项目(笔记)
  5. Android自定义事件总线,android事件总线EventBus3.0使用方法详解
  6. 《如何搭建小微企业风控模型》第四节 了解数据源
  7. 三层架构下,优酷视频搜索测试体系很复杂吗?
  8. mysql 从库升级为主库的步骤
  9. listview 中内容覆盖了屏幕底部的工具栏的解决办法
  10. python 读png的值变了_深度学习数据预处理_python批量转换labelme标注的json格式标签为png格式...
  11. SQL获得当前时间函数(MySQL)
  12. PMP模拟考试系统-pmp模拟考试题库
  13. 登陆界面HTML验证码生成
  14. 怎么查看和下载高清谷歌卫星地图
  15. python大规模获取豆瓣影评_python自动获取豆瓣电影评分和影评
  16. 人脸图像数据库(完整版)
  17. 帝国cms !--list.var1--,!--list.var2--的终极用法
  18. iOS16 中的 3 种新字体宽度样式
  19. Elasticsearch-8.4.2 集群安装
  20. Nvidia Isaac Sim ROS机器人仿真和AMR开发环境

热门文章

  1. Android加密篇 RSA
  2. 20200228视频播放器的字幕支持
  3. 2020年国考行测错题集(省级)
  4. 中国机器人最新统计数据发布及分析(附图)
  5. 如何获取Linux-gate.so.1动态库
  6. Alpine的安装、介绍与相关配置
  7. 修改IDEA默认配置路径
  8. SpringBoot【整合JSP】
  9. 三角形外接球万能公式_三棱锥外接球半径公式
  10. 化合物纯度、溶剂溶解度检测