
《Spring6核心技术》第11章:深度解析@Value注解
本节目录如下所示:
- 学习指引
- 注解说明
- 注解源码
- 使用场景
- 注解用法
- 使用案例
- 源码时序图
- 解析并获取@Value修饰的属性
- 为@Value修饰的属性赋值
- 使用@Value获取属性的值
- 源码解析
- 解析并获取@Value修饰的属性
- 为@Value修饰的属性赋值
- 使用@Value获取属性的值
- 总结
- 思考
- VIP服务
一、学习指引
Spring中的@Value注解,你真的彻底了解过吗?
在实际开发过程中,通常会有这样一种场景:将一些配置项写到配置文件中,在业务逻辑中会读取配置文件中的配置项,取出对应的值进行业务逻辑处理。Spring中提供的@Value注解就可以读取配置文件中的值。另外@Value注解也可以向Bean中的属性设置其他值。本章,就对@Value注解进行简单的介绍。
二、注解说明
关于@Value注解的一点点说明~~
@Value注解可以向Spring的Bean的属性中注入数据。并且支持Spring的EL表达式,可以通过${} 的方式获取配置文件中的数据。配置文件支持properties、XML、和YML文件。
2.1 注解源码
@Value注解的源码详见:org.springframework.beans.factory.annotation.Value。
从源码可以看出,@Value注解可以标注到字段、方法、参数和其他注解上,@Value注解中提供了一个String类型的value属性,具体含义如下所示。
- value:指定要向Bean的属性中注入的数据,数据可以是配置文件中的配置项,并且支持EL表达式。
2.2 使用场景
在实际开发中,项目中难免会有一些配置信息,此时,就可以将这些配置信息统一写到配置文件中。随后使用@Value注解读取配置文件的值来向Spring中Bean的属性设置值。
例如,一些系统环境变量信息,数据库配置,系统通用配置等等,都可以保存到配置文件中,此时就可以使用Spring的EL表达式读取配置文件中的值。
2.3 注解用法
本节,主要介绍不通过配置文件注入属性和通过配置文件注入属性两种情况来介绍@Value注解的用法。
1.不通过配置文件注入属性
通过@Value可以将外部的值动态注入到Bean中,有如下几种用法。
(1)注入普通字符串
(2)注入操作系统属性
(3)注入表达式的结果信息
(4)注入其他Bean属性
(5)注入文件资源
(6)注入URL资源
2.通过配置文件注入属性
通过@Value(“${app.name}”)语法将属性文件的值注入到bean的属性中,
3.@Value中#{...}
和${...}
的区别
这里提供一个测试属性文件:test.properties,大致的内容如下所示。
测试类Test:引入test.properties文件,作为属性的注入。
4.${...}
的用法
{}
里面的内容必须符合SpEL表达式, 通过@Value(“${spelDefault.value}”)可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,则会报错。可以通过赋予默认值解决这个问题,如下所示。
上述代码的含义表示向Bean的属性中注入配置文件中的author.name属性的值,如果配置文件中没有author.name属性,则向Bean的属性中注入默认值binghe。例如下面的代码片段。
5.#{…}
的用法
(1)SpEL:调用字符串Hello World的concat方法
(2)SpEL: 调用字符串的getBytes方法,然后调用length属性
6.${…}
和#{…}
混合使用
${...}
和#{...}
可以混合使用,如下文代码执行顺序:传入一个字符串,根据 "," 切分后插入列表中, #{}
和${}
配合使用,注意单引号。
注意:${}
和#{}
混合实用时,不能${}
在外面,#{}
在里面。因为Spring执行${}
的时机要早于#{}
,当Spring执行外层的${}
时,内部的#{}
为空,会执行失败。
7.@Value注解用法总结
-
#{…}
用于执行SpEl表达式,并将内容赋值给属性。 -
${…}
主要用于加载外部属性文件中的值。 -
#{…}
和${…}
可以混合使用,但是必须#{}
外面,${}
在里面。
三、使用案例
@Value的实现案例,我们一起实现吧~~
本节,就基于@Value注解实现向Bean属性中赋值的案例,具体的实现步骤如下所示。
(1)新增test.properties配置文件
在spring-annotation-chapter-11工程下的resources目录下新增test.properties配置文件,内容如下所示。
(2)新增ValueName类
ValueName类的源码详见:spring-annotation-chapter-11工程下的io.binghe.spring.annotation.chapter11.bean.ValueName。
可以看到,ValueName类上标注了@Component注解,说明当Spring的IOC容器启动时,会向IOC容器中注入ValueName类的Bean对象。
(3)新增ValueConfig类
ValueConfig类的源码详见:spring-annotation-chapter-11工程下的io.binghe.spring.annotation.chapter11.config.ValueConfig。
可以看到,在ValueConfig类上标注了@Configuration注解,说明ValueConfig类是Spring的配置类。使用@ComponentScan注解指定了扫描的包名是io.binghe.spring.annotation.chapter11。并且使用@PropertySource注解导入了test.properties配置文件。ValueConfig类的字段通过@Value注解注入对应的属性值,代码中有详细的注释,这里不再赘述。
(4)新增ValueTest类
ValueTest类的源码详见:spring-annotation-chapter-11工程下的io.binghe.spring.annotation.chapter11.ValueTest。
可以看到,ValueTest类是案例程序的测试类,实现的代码比较简单,这里不再赘述。
(5)运行ValueTest类
运行ValueTest类的main()方法,输出的结果信息如下所示。
可以看到,在ValueTest类中的各个字段值都输出了正确的结果数据。
说明:使用@Value注解向Bean的属性中正确设置了值。
四、源码时序图
结合时序图理解源码会事半功倍,你觉得呢?
本节,就以源码时序图的方式,直观的感受下@Value注解在Spring源码层面的执行流程。本节,会从解析并获取 @Value 修饰的属性、为 @Value 修饰属性赋值和使用@Value获取属性值三个方面分析源码时序图。
注意:本节以单例Bean为例分析源码时序图,并且基于@Value注解标注到类的字段上的源码时序图为例进行分析,@Value注解标注到类的方法上的源码时序图与标注到字段上的源码时序图基本相同,不再赘述。
4.1 解析并获取@Value修饰的属性
本节,就简单介绍下解析并获取@Value修饰的属性的源码时序图,整体如图11-1~11-2所示。
由图11-1~11-2可以看出,解析并获取@Value修饰的属性的流程中涉及到ValueTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、DefaultListableBeanFactory类、AbstractBeanFactory类、AbstractAutowireCapableBeanFactory类和AutowiredAnnotationBeanPostProcessor类。具体的源码执行细节参见源码解析部分。
4.2 为@Value修饰的属性赋值
本节,就简单介绍下为@Value修饰的属性赋值的源码时序图,整体如图11-3~11-4所示。
由图11-3~11-4所示,为@Value修饰的属性赋值流程涉及到ValueTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、DefaultListableBeanFactory类、AbstractBeanFactory类、AbstractAutowireCapableBeanFactory类、AutowiredAnnotationBeanPostProcessor类、InjectionMetadata类和AutowiredFieldElement类。具体的源码执行细节参见源码解析部分。
4.3 使用@Value获取属性的值
本节,就简单介绍下使用@Value注解获取属性的值的源码时序图,整体如图11-5~11-7所示。
由图11-5~11-7所示,使用@Value获取属性的值的流程涉及到ValueTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、DefaultListableBeanFactory类、AbstractBeanFactory类、AbstractAutowireCapableBeanFactory类、AutowiredAnnotationBeanPostProcessor类、InjectionMetadata类、AutowiredFieldElement类、AbstractEnvironment类、AbstractPropertyResolver类、PropertyPlaceholderHelper类和PropertySourcesPropertyResolver类。具体的源码执行细节参见源码解析部分。
五、源码解析
源码时序图整清楚了,那就整源码解析呗!
本节,主要分析@Value注解在Spring源码层面的执行流程,同样的,本节也会从解析并获取 @Value 修饰的属性、为 @Value 修饰属性赋值和使用@Value获取属性值三个方面分析源码执行流程,并且结合源码执行的时序图,会理解的更加深刻。
注意:本节以单例Bean为例分析,并且基于@Value注解标注到类的字段上的源码流程为例进行分析,@Value注解标注到类的方法上的源码流程与标注到字段上的源码流程基本相同,不再赘述。
5.1 解析并获取@Value修饰的属性
本节主要对解析并获取 @Value 修饰属性的源码流程进行简单的分析,结合源码执行的时序图,会理解的更加深刻,本节的源码执行流程可以结合图11-1~11-2进行理解。具体分析步骤如下所示。
注意:解析并获取 @Value 修饰属性源码流程的前半部分与第7章5.3节分析源码的流程相同,这里,从AbstractBeanFactory类的doGetBean()方法开始分析。
(1)解析AbstractBeanFactory类的doGetBean(String name, ClassrequiredType, Object[] args, boolean typeCheckOnly)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, ClassrequiredType, Object[] args, boolean typeCheckOnly)。重点关注如下代码片段。
可以看到,在AbstractBeanFactory类的doGetBean()方法中,如果是单例Bean,会调用getSingleton()方法创建单例Bean,实际执行的是Lambda表达式中的createBean()方法来创建单例Bean。
(2)解析AbstractAutowireCapableBeanFactory类的createBean(String beanName, RootBeanDefinition mbd, Object[] args)。
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。
可以看到,在AbstractAutowireCapableBeanFactory类的createBean()方法中,会调用doCreateBean()方法创建Bean对象。
(3)解析AbstractAutowireCapableBeanFactory类的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。此时重点关注创建Bean实例的代码片段,如下所示。
(4)解析AbstractAutowireCapableBeanFactory类的(String beanName, RootBeanDefinition mbd, Object[] args)方法
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args)。
可以看到,createBeanInstance()方法会创建Bean的实例并返回BeanWrapper对象。
(5)返回AbstractAutowireCapableBeanFactory类的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法,此时,重点关注如下代码片段。
可以看到,在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中会调用applyMergedBeanDefinitionPostProcessors()方法的主要作用就是:获取@Value、@Autowired、@PostConstruct、@PreDestroy等注解标注的字段和方法,然后封装到InjectionMetadata对象中,最后将所有的InjectionMetadata对象存入injectionMeatadataCache缓存中。
(6)解析AbstractAutowireCapableBeanFactory类的applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName)方法
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName)。
可以看到,在AbstractAutowireCapableBeanFactory类的applyMergedBeanDefinitionPostProcessors()方法中,会调用processor的postProcessMergedBeanDefinition()方法处理BeanDefinition信息。
(7)解析AutowiredAnnotationBeanPostProcessor类postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName)。
源码详见:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName)
可以看到,在AutowiredAnnotationBeanPostProcessor类postProcessMergedBeanDefinition()方法中会调用findInjectionMetadata()方法来获取标注了注解的字段或者方法。
(8)解析AutowiredAnnotationBeanPostProcessor类的findInjectionMetadata(String beanName, Class<?> beanType, RootBeanDefinition beanDefinition)
源码详见:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findInjectionMetadata(String beanName, Class<?> beanType, RootBeanDefinition beanDefinition)。
可以看到,在AutowiredAnnotationBeanPostProcessor类的findInjectionMetadata()方法中,调用了findAutowiringMetadata方法来解析并获取@Value、@Autowired、@Inject等注解修饰的属性或者方法。
(9)解析AutowiredAnnotationBeanPostProcessor类的findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs)方法
源码详见:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs)。
AutowiredAnnotationBeanPostProcessor类的findAutowiringMetadata()方法需要重点关注下,findAutowiringMetadata()方法最核心的功能就是对传递进来的每个类进行筛选判断是否被@Value、@Autowired、@Inject注解修饰的方法或者属性,如果是被 @Value、@Autowired、@Inject注解修饰的方法或者属性,就会将这个类记录下来,存入injectionMetadataCache缓存中,为后续的 DI 依赖注作准备。
首次调用findAutowiringMetadata()方法时,会调用buildAutowiringMetadata()方法来查找使用@Value、@Autowired、@Inject注解修饰的方法或者属性。
(10)解析AutowiredAnnotationBeanPostProcessor类的buildAutowiringMetadata(Class<?> clazz)方法。
源码详见:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata(Class<?> clazz)。方法中会查找@Value、@Autowired、@Inject注解修饰的方法或者属性,这里以查找属性为例,重点关注如下代码片段。
可以看到,在AutowiredAnnotationBeanPostProcessor类的buildAutowiringMetadata()方法中,获取到类上所有的字段,然后遍历每个字段,判断是否标注了 @Value、@Autowired和@Inject注解,如果标注了 @Value、@Autowired和@Inject注解,直接封装成 AutowiredFieldElement 对象,然后保存到一个名为 currElements集合中。
指的一提的是,如果解析到的字段是静态字段,则直接返回,这就是为什么Spring不会对类中的静态字段赋值的原因。如下代码片段所示。
在AutowiredAnnotationBeanPostProcessor类的buildAutowiringMetadata()方法的最后,则将标注了@Value、@Autowired和@Inject注解的字段封装到 InjectionMetadata 对象中,如下所示。
最终回到AutowiredAnnotationBeanPostProcessor类的findAutowiringMetadata()方法中,将InjectionMetadata 对象存入injectionMetadataCache缓存中。如下所示。
另外,在AutowiredAnnotationBeanPostProcessor类的buildAutowiringMetadata()方法中,调用了findAutowiredAnnotation()方法来获取注解信息,如下所示。
接下来,看看这个findAutowiredAnnotation()方法。
(11)解析AutowiredAnnotationBeanPostProcessor类的findAutowiredAnnotation(AccessibleObject ao)方法
源码详见:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation(AccessibleObject ao)。
可以看到,在AutowiredAnnotationBeanPostProcessor类的findAutowiredAnnotation()方法中,会遍历autowiredAnnotationTypes集合,通过遍历出的每个autowiredAnnotationTypes集合中的元素从annotations中获取MergedAnnotation对象annotation,如果annotation存在,则返回annotation。否则返回null。
这里,需要关注下autowiredAnnotationTypes集合,在AutowiredAnnotationBeanPostProcessor类的构造方法中向autowiredAnnotationTypes集合中添加元素,如下所示。
可以看到,在AutowiredAnnotationBeanPostProcessor类的构造方法中,向autowiredAnnotationTypes集合中添加了@Autowired注解、@Value注解和@Inject注解。所以,@Autowired注解赋值的流程和@Value注解赋值的流程基本一致。
至此,解析并获取@Value注解修饰的属性的源码流程分析完毕。
文章转载自微服务:冰河技术
