概述

在项目中大家应该都是用过@Value注解读取配置文件中的值,实际上@Value的功能原比想象中的强大。本文主要针对@Value的注解做一个全面的总结,同时通过源码分析它的实现原理。

@Value注解介绍和使用

介绍

  1. @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Value {
  5. /**
  6. * The actual value expression such as <code>#{systemProperties.myProp}</code>
  7. * or property placeholder such as <code>${my.app.myProp}</code>.
  8. */
  9. String value();
  10. }
  • @Value只有一个属性value,可以理解为取值
  • 该注解可以作用于属性字段、方法、入参、其他注解上。

    使用场景

    该注解主要可以将外部的值注入到Bean中,概括来有以下几种场景:

    注入配置文件属性

    1. public class ValueTestBean {
    2. @Value("${bsfit.user.user-name:alvin}")
    3. private String userName;
    4. @Value("${bsfit.user.age}")
    5. private Integer age;
    6. private String sex;
    7. private String userId;
    8. @Value("${bsfit.user.sex}")
    9. public void setSex(String sex) {
    10. this.sex = sex;
    11. }
    12. @Autowired
    13. public void setUserId(@Value("${bsfit.user.userId}") String userId) {
    14. this.userId = userId;
    15. }
    16. }

    配置如下:

    1. bsfit.user:
    2. user-name: ${person.last-name}
    3. age: 55
    4. userId: E000001
    5. sex: boy

    输出结果:

    1. ValueTestBean(userName=alvinbaf65cc0-3728-42d0-9255-19cad89fcb7a, age=55, sex=boy, userId=E000001)
  • 使用最多的一种场景,从配置文件读取数据,格式:${userName:defaut_value},冒号后面表示如果读取不到userName的值,则用默认值。

  • 不支持松散绑定,也就是说比如配置中写user-id@Value中也必须写user-id

    注入SPEL表达式结果

    1. @Value("#{ T(java.lang.Math).random() * 100.0 }")
    2. private double randomNumber; //注入表达式结果
  • @Value支持SPEL表达式,它的具体用法参考:https://cloud.tencent.com/developer/article/1676200

    注入系统属性

    1. @Value("#{systemProperties['os.name']}")
    2. private String systemPropertiesName; // 注入操作系统属性
  • 可以通过System.getProperties()方法获取操作系统有哪些属性

    注入其他Bean的属性

    1. @Value("#{person.birth}")
    2. private Date personBirth;
  • person是其他bean的名称

    注入字符串字面量

    ```java @Component public class ValueTestBean {

    @Value(“hello”) private String plainStr;

}

  1. - 直接写字符串,那么最终plainStr的值就是`hello`
  2. <a name="DPDNl"></a>
  3. #### 注入文件资源
  4. ```java
  5. @Value("classpath:person.properties")
  6. private Resource resourceFile; // 注入文件资源

注入URL资源

  1. @Value("http://www.baidu.com")
  2. private Resource url; // 注入URL资源

最终全量的输出结果:
image.png
代码地址:
https://github.com/alvinlkk/springboot-demo/tree/master/springboot-02-config

使用注意点

  1. @Value可以使用${xxx}或者#{xxx}, 比如@Value("${user.name:alvin}")获取属性文件中对应的值, 而@Value("#{'Hello World'.concat('!')}")可以写复杂的SPEL表达式。
  2. ${…}和#{…}可以混合使用,但是#中可以包含$, $中不能包含#。比如正确的使用:@Value("#{'${server.name}'.split(',')}"),错误的使用:@Value("${#{'HelloWorld'.concat('_')}}")
  3. @Value("${user.name}")如果user.name获取不到,启动会报错,我们可以冒号带上默认值@Value("${user.name:alvin}"),这样就不会报错了。
  4. 类型转换报错,如果配置中读取的是String类型字符串,绑定到int类型上,会报错。
  5. 区别于@ConfigrationProperties,@Value不支持松散绑定, 意味着配置文件写的user-id,代码中也要用user-id读取。

    源码解析

    我们重点关注下最常见的一个场景,从配置文件中获取值是如何实现的。

    1. @Value("${bsfit.user.user-name:alvin}")
    2. private String userName;

    执行主流程

    我们看@Value注解的源码,他的注释其实提示了我们是由哪个类来处理的,如下:
    image.png
    所以我们就优先重点关注这个类AutowiredAnnotationBeanPostProcessor,根据这个类的javadoc,它说明了该类实现了BeanPostProcessor接口,处理通过@Autowird@Value注入属性,我们重点关注在@Value上。
    整个属性的注入其实是在Bean的初始化阶段,而AutowiredAnnotationBeanPostProcessor是一个初始化的前后置处理器,所以整个执行流程如下: 一文深入了解Spring中的@Value注解 - 图3上面就是整个@Value属性的时序图。

  6. 通过调用AbstractAutowireCapableBeanFactorypopulateBean方法在Bean的初始化阶段填充属性值。

  7. populateBean方法中调用了InstantiationAwareBeanPostProcessor接口的postProcessProperties回调方法处理真正的属性填充,InstantiationAwareBeanPostProcessor的实现类很多,其中的AutowiredAnnotationBeanPostProcessor实现是用来处理属性填充的。

    1. protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    2. .......
    3. for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
    4. // 调用postProcessProperties方法处理属性
    5. PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    6. if (pvsToUse == null) {
    7. if (filteredPds == null) {
    8. filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
    9. }
    10. pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
    11. if (pvsToUse == null) {
    12. return;
    13. }
    14. }
    15. pvs = pvsToUse;
    16. }
    17. .......
    18. }

    �3. 下图是AutowiredAnnotationBeanPostProcessor中的postProcessProperties方法,用来处理属性。
    image.png
    下面是InjectionMetadata类的inject方法,也就是上面调用的下来的。
    image.png
    接着调用AutowiredFieldElement类的inject方法注入,如下图:
    image.png

  8. 最后我们重点关注它是如何解析出值的,我们关注在AutowiredFieldElement#resolveFieldValue()方法。

image.png
接着看DefaultListableBeanFactory#resolveDependency方法。
image.png
DefaultListableBeanFactory#doResolveDependency方法是真正执行变量值获取的方法。
image.png
以上是执行的一个大致主流程,很多细节还是要大家自己debug一步一步去执行。

小结

@Value还是一个使用频率很高的注解,它支持SPEL表达式,功能强大。

参考

https://blog.csdn.net/qq_40837310/article/details/105960560
https://blog.csdn.net/hry2015/article/details/72353994