目录
前置说明
本文 不是 一篇关于@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 配置的使用
使用示例
- 配置文件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"}
- 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
}
- 引用
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),对于多层就无能无力了,所以list
和map
的多层表达方式,@Value是不能正确识别的。
那如何在@Value上使用list和map呢?因为@Value可以使用SpEL
,可以借助这个特性完成。
修改:
- 修改配置文件,增加两条单行记录。将数组做成单行(其实是识别成字符串),将对象改成字符串(加
'
或"
):
value-list-property: 1111,2222,3333
value-map-property: '{"k1": "123", "k2": "456"}'
- 修改代码:
@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。