“ 通过 @Value 我们可以将配置文件中的数据注入到属性或入参中,Spring 是怎样解析注入的呢?”
01
—
配置项注入
用法示例
@Value("${value.from.file:defaultValue}")private String value;
value.from.file 为配置文件的 key ,如果没有的时候用字符串 "defaultValue" 代替。
# bool 值@Value("${some.key:true}")private boolean booleanWithDefaultValue;# 数组@Value("${some.key:one,two,three}")private String[] stringArrayWithDefaults;
流程分析
首先在 Bean 创建后,BeanFactory 会填充 Bean 中的属性值,包含 Autowired、Value 等
我们以AnnotationConfigApplicationContext 创建为示例来说明。
public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}
创建 Context 的构造函数中创建了两个对象:
ClassPathBeanDefinitionScanner:通过Java字节码来扫描包中 class 文件,来生成 BeanDefinition。具体扫描过程可以参考上一篇文章Spring 中注解扫描。
AnnotatedBeanDefinitionReader:这个构造函数中会创建 Environment,代码如下:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, getOrCreateEnvironment(registry));}
getOrCreateEnvironment 会创建 StandardEnvironment,这个时候会初始化两个 PropertySource。
protected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}
至此 Environment 创建好了,我们为什么要讲 Environment 创建呢?
Spring 中解析配置是通过 Environment.resolvePlaceholders 来进行解析的,创建完 Environment 后,接下来就是解析配置的文件,将 Property 放入到 Environment 中。
PropertyResolver:接口中定义了 resolvePlaceholders
ConfigurablePropertyResolver:可以自定义表达式格式,前缀格式( "${" ),后缀格式( "}" ),默认值分隔符( ":" )
AbstractEnvironment:实现了 resolvePlaceholders 逻辑。

AbstractEnvironment.resolvePlaceholders 代码如下。

propertyResolver 创建的时候,将 propertySources 放了进去。MutablePropertySources 通过名称我们就可以看出这个类可以包含多个配置文件。propertyResolver 解析配置的时候会按顺序查找,命中后就直接返回。
如下图所示,MutablePropertySources 中 0 和 1 是 Environment 创建的时候默认初始化的配,第二个是我们自己加的配置,这里我们也可以将我们自己加的配置放在最前面。MutablePropertySources 中有 addFirst 和 addLast 来控制添加的位置。

02
—
SpEL表达式
用法示例
从系统配置中取值
@Value("#{systemProperties['unknown'] ?: 'some default'}")private String spelSomeDefault;
取某个 Bean 的属性
@Value("#{someBean.someValue}")private Integer someBeanValue;
将配置转成 List
@Value("#{'${listOfValues}'.split(',')}")private List<String> valuesList;
其他用法
# valuesMap={key1: '1', key2: '2', key3: '3'}@Value("#{${valuesMap}}")private Map<String, Integer> valuesMap;@Value("#{${valuesMap}.key1}")private Integer valuesMapKey1;# 这种情况 key 不存在不会抛异常@Value("#{${valuesMap}['unknownKey']}")private Integer unknownMapKey;@Value("#{${valuesMap}['unknownKey'] ?: 5}")private Integer unknownMapKeyWithDefaultValue;@Value("#{${valuesMap}.?[value>'1']}")private Map<String, Integer> valuesMapFiltered;
流程分析
DefaultListableBeanFactory.doResolveDependency 这个方法主要是对需要注入属性值进行解析,比如带 @Autowire、@Value 的属性。其中有如下一段代码:
if (value instanceof String) {String strVal = resolveEmbeddedValue((String) value); ①BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);value = evaluateBeanDefinitionString(strVal, bd); ②}
①:是对配置进行解析,就是我们上面第一部分讲的,如果这里没有获取到会将 value 值原样返回。
②:这部分是将 strVal 当做 SpEL 表达式进行解析。
从这里可以看出我们不仅可以直接将表达式配置在 @Value 注解中,还可以配置在配置文件中。
# test.spel=#{ systemProperties['java.vm.name'] }@Value("${test.spel}")private String value;
SpEL 的更多用法可以参考官方文档
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions
长按二维码关注,获得更多干货文章





