一文全面深入了解Spring中的@Value注解

简介: 一文全面深入了解Spring中的@Value注解

概述


在项目中大家应该都是用过@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("#{systemProperties['os.name']}")
 private String systemPropertiesName; // 注入操作系统属性
  • 可以通过System.getProperties()方法获取操作系统有哪些属性


注入其他Bean的属性


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


注入字符串字面量


@Component
public class ValueTestBean {
    @Value("hello")
    private String plainStr;
}
  • 直接写字符串,那么最终plainStr的值就是hello


注入文件资源


@Value("classpath:person.properties")
private Resource resourceFile; // 注入文件资源


注入URL资源


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


最终全量的输出结果:

image.png


代码地址:

github.com/alvinlkk/sp…


使用注意点


  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读取。


源码解析


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

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


执行主流程


我们看@Value注解的源码,他的注释其实提示了我们是由哪个类来处理的,如下:

image.png

所以我们就优先重点关注这个类AutowiredAnnotationBeanPostProcessor,根据这个类的javadoc,它说明了该类实现了BeanPostProcessor接口,处理通过@Autowird@Value注入属性,我们重点关注在@Value上。

整个属性的注入其实是在Bean的初始化阶段,而AutowiredAnnotationBeanPostProcessor是一个初始化的前后置处理器,所以整个执行流程如下:

image.png

上面就是整个@Value属性的时序图。

  1. 通过调用AbstractAutowireCapableBeanFactorypopulateBean方法在Bean的初始化阶段填充属性值。
  2. 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;
      }
           .......
    }
  1. 下图是AutowiredAnnotationBeanPostProcessor中的postProcessProperties方法,用来处理属性。

image.png

下面是InjectionMetadata类的inject方法,也就是上面调用的下来的。

image.png

接着调用AutowiredFieldElement类的inject方法注入,如下图:

image.png

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

image.png

接着看DefaultListableBeanFactory#resolveDependency方法。

image.png

DefaultListableBeanFactory#doResolveDependency方法是真正执行变量值获取的方法。

image.png

以上是执行的一个大致主流程,很多细节还是要大家自己debug一步一步去执行。


小结


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

目录
相关文章
|
1天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
100 73
|
1天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
1天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
27天前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
41 4
|
27天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
103 2
|
27天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
46 2
|
27天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
69 0
|
7月前
|
Java API Spring
Spring容器如何使用一个注解来指定一个类型为配置类型
Spring容器如何使用一个注解来指定一个类型为配置类型
61 0
|
2月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
53 0
|
6月前
|
XML Java 数据格式
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
62 0