Spring Boot 配置@ConfigurationProperties和@Value的对比总结

前置说明

本文 不是 一篇关于@ConfigurationProperties和@Value的使用指南,主要总结使用两者的区别,和在复杂类型上的方法与不同。

@ConfigurationProperties和@Value对比

先来看一段官方的说明对两者做个对比,以下引用Spring Boot官方说明文档4.2.8


@Value注解是一个核心容器特性,它不提供与类型安全配置属性 (type-safe configuration properties[1]) 相同的特性。下表总结了@ConfigurationProperties 和 @Value 支持的特性:

特性@ConfigurationProperties@Value
宽松绑定
Relaxed binding
支持不支持
元数据的支持
Meta-data support
支持不支持
SpEL表达式
SpEL evaluation
不支持支持

如果你为自己的的组件定义一组配置项,我们建议你将它们分组到一个带有@ConfigurationProperties注解的POJO中。你可能还意识到,由于@Value不支持宽松绑定(Relaxed binding),当你需要使用环境变量提供值时,它不是一个好的候选方案。

最后,尽管你可以在@Value上使用SpEL表达式,但此类表达式不会被应用程序的配置文件处理。


注[1] type-safe configuration properties: 即@ConfigurationProperties对配置的类型和多层结构自动发现的特性

@ConfigurationProperties 配置的使用

使用示例

  1. 配置文件yml
config-demo:
  string-property: 这是个字符串
  bool-property: false
  int-property: 128
  date-property: 2019/12/08 22:42:00
  list-property:
    - 1111
    - 2222
    - 3333
  map-property: {"k1": "123", "k2": "456"}
  1. POJO对象
@Component
@ConfigurationProperties(prefix = "config-demo")
public class ConfigDemo {
    private String stringProperty;
    private boolean boolProperty;
    private int intProperty;
    private Date dateProperty;
    private List<String> listProperty;
    private Map<String, String> mapProperty;
    
    // Getter and Setter
}
  1. 引用spring-boot-configuration-processor
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

测试使用

创建一个启动时调用函数

@Component
public class ConfigDemoRun implements ApplicationRunner{

    private static ObjectMapper mapper = new ObjectMapper();
    
    static {
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
    }
    
    @Autowired
    private ConfigDemo configDemo;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(mapper.writeValueAsString(configDemo));
    }
}

执行输出如下:

{
  "stringProperty" : "这是个字符串",
  "boolProperty" : false,
  "intProperty" : 128,
  "dateProperty" : 1575816120000,
  "listProperty" : [ "1111", "2222", "3333" ],
  "mapProperty" : {
    "k1" : "123",
    "k2" : "456"
  }
}

@Value 配置的使用

@Value 对普通类型的支持

同样使用刚才的配置文件,创建一个@Value读取配置的POJO:

@Component
public class ValueDemo {
    @Value("${config-demo.string-property}")
    private String stringProperty;
    @Value("${config-demo.bool-property}")
    private boolean boolProperty;
    @Value("${config-demo.int-property}")
    private int intProperty;
    @Value("${config-demo.date-property}")
    private Date dateProperty;
    
    // Getter and Setter
}

以上字符串、布尔、整型、日期都是可以读取的,与@ConfigurationProperties并无差异。

@Value使用list和map

如何使用@Value配置list或map呢?如果只是将代码像如下修改,是不能正常工作的:

@Component
public class ValueDemo {
    // ...
    
+   @Value("${config-demo.list-property}")
+   private List<String> listProperty;

+   @Value("${config-demo.map-property}")
+   private Map<String, String> mapProperty;
    
    // Getter and Setter
}

报错如下:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoRun': Unsatisfied dependency expressed through field 'valueDemo'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'valueDemo': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'config-demo.list-property' in value "${config-demo.list-property}"
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at pers.zeal.springboot.demo.SpringBootConfigDemoApplication.main(SpringBootConfigDemoApplication.java:10) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'valueDemo': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'config-demo.list-property' in value "${config-demo.list-property}"
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1287) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	... 18 common frames omitted
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'config-demo.list-property' in value "${config-demo.list-property}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:178) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:236) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:908) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1228) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	... 29 common frames omitted

因@Value不支持宽松绑定(Relaxed binding),对于多层就无能无力了,所以listmap的多层表达方式,@Value是不能正确识别的。

那如何在@Value上使用list和map呢?因为@Value可以使用SpEL,可以借助这个特性完成。

修改:

  1. 修改配置文件,增加两条单行记录。将数组做成单行(其实是识别成字符串),将对象改成字符串(加'"):
   value-list-property: 1111,2222,3333
   value-map-property: '{"k1": "123", "k2": "456"}'
  1. 修改代码:
    @Value("#{'${config-demo.value-list-property}'.split(',')}")
    private List<String> listProperty;
    
    @Value("#{${config-demo.value-map-property}}")
    private Map<String, String> mapProperty;

运行测试代码,发现已经可以正确识别。

  • 使用string.split,分解成数组
  • 使用将json字符串引用成对象,来识别成map

更多SpEL的使用可以参考官方说明= Spring Expression Language (SpEL)

总结

如何在工程中使用以上两者呢?

  • @Value 正如官方描述的一样,它是单个参数注入(inject single application arguments),当简单的业务,只需要一个或简单的参数时,可以使用这种方式。
  • @ConfigurationProperties 当一个配置需要由多个参数构成时,使用这种方式识别成一个POJO,即方便控制,又方便扩展。

完整的本文章的示例代码,可以访问https://gitee.com/zeal-zhang/spring-boot-demo

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值