概述
在项目中大家应该都是用过@Value
注解读取配置文件中的值,实际上@Value
的功能原比想象中的强大。本文主要针对@Value
的注解做一个全面的总结,同时通过源码分析它的实现原理。
@Value注解介绍和使用
介绍
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
/**
* The actual value expression such as <code>#{systemProperties.myProp}</code>
* or property placeholder such as <code>${my.app.myProp}</code>.
*/
String value();
}
@Value
只有一个属性value,可以理解为取值-
使用场景
该注解主要可以将外部的值注入到Bean中,概括来有以下几种场景:
注入配置文件属性
public class ValueTestBean {
@Value("${bsfit.user.user-name:alvin}")
private String userName;
@Value("${bsfit.user.age}")
private Integer age;
private String sex;
private String userId;
@Value("${bsfit.user.sex}")
public void setSex(String sex) {
this.sex = sex;
}
@Autowired
public void setUserId(@Value("${bsfit.user.userId}") String userId) {
this.userId = userId;
}
}
配置如下:
bsfit.user:
user-name: ${person.last-name}
age: 55
userId: E000001
sex: boy
输出结果:
ValueTestBean(userName=alvinbaf65cc0-3728-42d0-9255-19cad89fcb7a, age=55, sex=boy, userId=E000001)
使用最多的一种场景,从配置文件读取数据,格式:
${userName:defaut_value}
,冒号后面表示如果读取不到userName的值,则用默认值。不支持松散绑定,也就是说比如配置中写
user-id
,@Value
中也必须写user-id
。注入SPEL表达式结果
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入表达式结果
@Value
支持SPEL表达式,它的具体用法参考:https://cloud.tencent.com/developer/article/1676200注入系统属性
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入操作系统属性
可以通过
System.getProperties()
方法获取操作系统有哪些属性注入其他Bean的属性
@Value("#{person.birth}")
private Date personBirth;
-
注入字符串字面量
```java @Component public class ValueTestBean {
@Value(“hello”) private String plainStr;
}
- 直接写字符串,那么最终plainStr的值就是`hello`。
<a name="DPDNl"></a>
#### 注入文件资源
```java
@Value("classpath:person.properties")
private Resource resourceFile; // 注入文件资源
注入URL资源
@Value("http://www.baidu.com")
private Resource url; // 注入URL资源
最终全量的输出结果:
代码地址:
https://github.com/alvinlkk/springboot-demo/tree/master/springboot-02-config
使用注意点
@Value
可以使用${xxx}
或者#{xxx}
, 比如@Value("${user.name:alvin}")
获取属性文件中对应的值, 而@Value("#{'Hello World'.concat('!')}")
可以写复杂的SPEL表达式。- ${…}和#{…}可以混合使用,但是#中可以包含$, $中不能包含#。比如正确的使用:
@Value("#{'${server.name}'.split(',')}")
,错误的使用:@Value("${#{'HelloWorld'.concat('_')}}")
。 @Value("${user.name}")
如果user.name获取不到,启动会报错,我们可以冒号带上默认值@Value("${user.name:alvin}")
,这样就不会报错了。- 类型转换报错,如果配置中读取的是String类型字符串,绑定到int类型上,会报错。
区别于
@ConfigrationProperties
,@Value
不支持松散绑定, 意味着配置文件写的user-id,代码中也要用user-id读取。源码解析
我们重点关注下最常见的一个场景,从配置文件中获取值是如何实现的。
@Value("${bsfit.user.user-name:alvin}")
private String userName;
执行主流程
我们看
@Value
注解的源码,他的注释其实提示了我们是由哪个类来处理的,如下:
所以我们就优先重点关注这个类AutowiredAnnotationBeanPostProcessor
,根据这个类的javadoc,它说明了该类实现了BeanPostProcessor
接口,处理通过@Autowird
和@Value
注入属性,我们重点关注在@Value
上。
整个属性的注入其实是在Bean的初始化阶段,而AutowiredAnnotationBeanPostProcessor
是一个初始化的前后置处理器,所以整个执行流程如下: 上面就是整个@Value
属性的时序图。通过调用
AbstractAutowireCapableBeanFactory
的populateBean
方法在Bean的初始化阶段填充属性值。populateBean
方法中调用了InstantiationAwareBeanPostProcessor
接口的postProcessProperties
回调方法处理真正的属性填充,InstantiationAwareBeanPostProcessor
的实现类很多,其中的AutowiredAnnotationBeanPostProcessor
实现是用来处理属性填充的。protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
.......
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
// 调用postProcessProperties方法处理属性
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
.......
}
�3. 下图是
AutowiredAnnotationBeanPostProcessor
中的postProcessProperties
方法,用来处理属性。
下面是InjectionMetadata
类的inject
方法,也就是上面调用的下来的。
接着调用AutowiredFieldElement
类的inject
方法注入,如下图:最后我们重点关注它是如何解析出值的,我们关注在
AutowiredFieldElement#resolveFieldValue()
方法。
接着看DefaultListableBeanFactory#resolveDependency
方法。DefaultListableBeanFactory#doResolveDependency
方法是真正执行变量值获取的方法。
以上是执行的一个大致主流程,很多细节还是要大家自己debug一步一步去执行。
小结
@Value
还是一个使用频率很高的注解,它支持SPEL表达式,功能强大。
参考
https://blog.csdn.net/qq_40837310/article/details/105960560
https://blog.csdn.net/hry2015/article/details/72353994