Spring 中 @Value 注解使用和源码分析

news2024/11/13 20:23:53

1、@Value 注解使用

先配置本地 application.properties 如下:

apple.name=abc

代码如下:


@PropertySource("application.properties")
public class Apple {
	@Value("${apple.name}")
	public String name;
}

@ComponentScan
public class AtValueTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtValueTest.class);
		Apple bean = context.getBean(Apple.class);
		System.out.println("bean.name = " + bean.name);
	}
}

结果如下:

bean.name = abc

可以看到最终在 Apple 中可以获取到配置文件中的值,那么 Spring 是怎样解析获取到该值的呢?下面开始分析下。

2、@Value 注解源码分析

分析源码之前,我么可以简单思考下, Spring 会怎么样去处理这个 @Value 注解的?然后再带着我们的问题去看看 Spring 是不是和我们想的一样呢?

简单分析有三个步骤,如下:

1、首先 Spring 肯定要去解析收集该注解,找到被 @Value 注解修饰的所有属性,注意此时属性的值是一个占位符 ${apple.name},并不是真正的值
 
2、然后加载一个资源文件,可以是本地 application.properties、也可以是其他,比如 Environment 、xml
 
3、最后去判断哪个类上的属性有 @Value 修饰,就把占位符替换成资源文件中配置的值

在脑海中有了大致思路,再去追踪源码就事半功倍了。

2.1、Spring 解析并收集 @Value 修饰的属性

Spring 解析收集 @Value 修饰的属性和解析收集 @Autowired 注解一模一样,懂这个,@Autowired 注解解析流程也就懂了。

Spring 提供很多 BeanFactoryPostProcessor、BeanPostProcessor 接口作为扩展,从而使 Spring 非常强大,因为我们属性赋值相当于是在实例化之后的事,所以这里的 @Value 解析就是 BeanPostProcessor 接口的应用啦!

一个大家非常熟悉的类 AutowiredAnnotationBeanPostProcessor,Spring 的 DI(依赖注入) 就是这个 BeanPostProcessor 完成的,接下来看 Spring 源码,如下:

可以看到 Spring 是在实例化之后开始去收集的,这个时序非常重要,一定要注意

在这里插入图片描述

然后进入 AutowiredAnnotationBeanPostProcessor 类核心部分,如下:

对过来的每个类进行筛选判断是否有被 @Value、@Autowired 修饰的方法或者属性,如果找到有,就会将这个类记录下来,放到一个 injectionMetadataCache 缓存中,为后续的 DI 依赖注作准备,注意哦,解析并收集到的结果最终放到 Spring 的 injectionMetadataCache 缓存中

在这里插入图片描述

进入 buildAutowiringMetadata() 方法内部,如下:

获取到这个类上所有的属性,然后遍历每个属性,判断是否有 @Value、@Autowired 修饰,如果有,直接封装成 AutowiredFieldElement 对象,然后保存到一个名为 currElements List 容器中

在这里插入图片描述

最后在封装到 InjectionMetadata 对象中,最终返回出去放到 injectionMetadataCache 缓存中保存,不用管他封装到哪个对象,反正这里就是扫描并解析到了哪些属性,或者方法后续需要做处理就可以了。

在这里插入图片描述

上面源码中 findAutowiredAnnotation() 方法内部逻辑如下:

在这里插入图片描述

AutowiredAnnotationBeanPostProcessor 类创建的时候,Spring 就默认往 autowiredAnnotationTypes 容器中添加两个元素,如下:

在这里插入图片描述

至此,解析收集 @Value 修饰的属性已经完成,最终将收集到的结果放到了 injectionMetadataCache 缓存中保存,后续需要使用直接可以从这个缓存中获取即可。

2.2、Spring 为 @Value 修饰属性赋值

上面通过 postProcessMergedBeanDefinition() 方法收集好了 @Value 注解修饰的属性,那么下面要做的就是去为这个属性进行赋值。

进入属性填充的方法,源码如下:

在这里插入图片描述在这里插入图片描述

然后进入到 AutowiredAnnotationBeanPostProcessor 类中,源码如下:

在这里插入图片描述

注意此时的 injectionMetadataCache 缓存中早已经有值了,因为前面我们就已经收集完成了 @Value 修饰的属性,所以这里直接从缓存中就可以获取到。

在这里插入图片描述

然后进入 metadata.inject(bean, beanName, pvs) 代码内部,如下:

在这里插入图片描述

最终通过 resolveFieldValue() 方法获取到属性值,然后通过反射 field.set() 方法给这个属性赋值,如下:

在这里插入图片描述

至此,整个 @Value 的流程就算完成,下面就是对这个 resolveFieldValue() 方法进一步分析,看下是怎么获取到属性值的,是怎么样将 $ 符号替换成解析成真正的值的

2.3、Spring $ 占位符替换成真正的值

继续深入分析 resolveFieldValue() 方法,核心源码如下:

在这里插入图片描述

下面这段逻辑是去解析 ${apple.name} 占位符的,这里面为什么需要递归 parseStringValue(),因为怕你出现这种形式的占位符 ${ ${apple.name} },最终获取到 key = apple.name,然后拿着这个 key 就去资源文件(xml、application.properties、Environment 等)中查找是否配置了这个 key 的值

注意这里是函数式写法,传入一个方法体 this::getPropertyAsRawString,后面会回调到这里。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

当调用 resolvePlaceholder() 方法时,回调到 getPropertyAsRawString() 方法,源码如下:

可以看到最终会调用到 getProperty() 方法获取到对应 key = apple.name 的值

在这里插入图片描述

propertySources 资源中获取 key = apple.name 的值,只要在这里获取到一个值就直接 return 出去即可

在这里插入图片描述

propertySources 这里会有三个,如下所示:

在这里插入图片描述

  • PropertiesPropertySource:封装操作系统属性键值对
  • SystemEnvironmentPropertySource:封装 JVM Environment 里面的键值对
  • ResourcePropertySource:封装 application.properties、xml 中键值对

然后 debug 发现最终是从 ResourcePropertySource 资源对象中获取到 apple.name 对应的值 abc,最终将 ${apple.name} 替换成真正的值 abc,最终通过反射将该值 abc 赋值到 Apple 类中的 name 属性上。

那么这里肯定有很多人会有这样的疑问?这三个对象是从哪里来的呢?如果要想知道这个,需要下面一些知道做铺垫,那么继续往下看 !

2.4、理解 PropertySource 和 MutablePropertySource

在 Spring 中需要加载一些额外的配置文件,比如操作系统相关的配置,JVM 环境变量相关的配置,自定义配置文件等等。那么这些文件加载到代码中可定要有一个类来封装它,这个类就是 PropertySource,先来看看 PropertySource 的源码如下:

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());
	
	// 给这个配置文件起个名字呗
	protected final String name;

    // 配置文件中所有的 key-value 键值对 
    // T 只要是 key-value 键值对即可,比如: Map、Properties 都可以
	protected final T source;

	public String getName() {
		return this.name;
	}

	public T getSource() {
		return this.source;
	}

    // 根据 name 获取到对应的配置文件
	@Nullable
	public abstract Object getProperty(String name);
}

看完这个 PropertySource 类的结构,我们看看上面三个类中封装的属性到底是啥?如下所示:

  • PropertiesPropertySource:封装操作系统属性键值对(os.name、file.encoding、user.name 等等)
     
    在这里插入图片描述

  • SystemEnvironmentPropertySource:封装 JVM Environment 里面的键值对(PATH、JAVA_HOME)
     
    在这里插入图片描述

  • ResourcePropertySource:封装 application.properties、xml 中键值对
     在这里插入图片描述

理解了 PropertySource 这个类之后,在来理解 MutablePropertySource 就非常容易。先来看看 MutablePropertySource 的源码,如下:

public class MutablePropertySources {

	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

}

从源码中可以看到,就是做了一个收集,将所有的 PropertySource 收集到一个 propertySourceList 容器中进行管理,至于为什么这样做呢?

个人理解是为了更方便的查找属性,将所有的资源文件汇集到一起,然后想要找一个 key = apple.name 就可以直接遍历一下所有的资源文件,看下这个 key 在哪个资源文件中能够找到,找到立即返回。

但是此时就会存在一个问题,那就是文件的加载顺序,可以发现最先加载 PropertiesPropertySource、其次 SystemEnvironmentPropertySource,最后才是自定义的配置文件 ResourcePropertySource!比如,我们在 application.properties 中配置 user.home=abc 如下所示:

user.home=abc
@Component
@PropertySource("application.properties")
public class Apple {
	@Value("${user.home}")
	public String name;
}

输出结果:

name = /Users/gongwm

并不是 abc,因为 PropertiesPropertySource 优先于 ResourcePropertySource 被加载。

下面这个源码是获取资源文件,可以发现只要获取到就会立即 return,所以最终决定权交给了往 propertySources 容器添加的顺序决定,那么我们来看看上面三个文件分别是在什么时候方进去的?

在这里插入图片描述

2.5、Spring 资源文件装载源码分析?

直接进入源码分析,如下:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

然后再 StandardEnvironment 的构造方法中,隐式调用父类 AbstractEnvironment 的构造方法,源码如下:

可以发现在这里直接 new 创建了 MutablePropertySources 对象

在这里插入图片描述在这里插入图片描述

最终可以发现这两个 systemProperties、systemEnvironment 资源文件都是在这里被加载的,添加到了 MutablePropertySources 对象中

在这里插入图片描述

对于自定义的配置文件在 ConfigurationClassPostProcessor 类中被加载,源码如下:

在这里插入图片描述在这里插入图片描述在这里插入图片描述

最终就是在这里被加载进去的,此时 propertySourceList 容器中就已经保存了三个资源文件,并且顺序是这样的PropertiesPropertySource(优先级最高) -> SystemEnvironmentPropertySource -> ResourcePropertySource (优先级最低)

在这里插入图片描述

最后有一点要注意,不要把 MutablePropertySourcesMutablePropertyValues 搞混了,两完全不是一一码事!具体想看 MutablePropertyValues 是啥,可以转到另一篇文章!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/99078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非零基础自学Golang 第9章 结构体 9.7 匿名结构体 9.8 小结 9.9 知识拓展

非零基础自学Golang 文章目录非零基础自学Golang第9章 结构体9.7 匿名结构体9.7.1 匿名结构体定义与初始化9.7.2 匿名结构体的应用9.8 小结9.9 知识拓展9.9.1 使用结构体解析XML文件第9章 结构体 9.7 匿名结构体 9.7.1 匿名结构体定义与初始化 匿名结构体&#xff0c;顾名思…

blender源代码编译运行

其实在blender官网上已经给出了编译步骤https://wiki.blender.org/wiki/Building_Blender/Windows&#xff0c;由于在源码编译的过程中还遇到了很多问题&#xff0c;特此记录一下。 文章目录前提准备代码下载1. blender源码下载2. lib下载编译前提准备 Visual Studio2019或者2…

Innodb存储引擎-执行流程分析和二阶段提交分析

文章目录执行流程分析基本流程WAL(Write-Ahead Log)write和fsync区别innodb_flush_logs_at_trx_commit & sync_binlog二阶段提交执行流程分析 基本流程 &#xff08;1&#xff09;连接,分析,优化,执行 客户端与MySQL Server建立连接&#xff0c;发送语句给MySQL Server&a…

2022.12.18 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.RNNConventional Recurrent Neural Networks5.Deep Recurrent Neural Networks5.1 Deep Transition RNN5.2 Deep Output5.3 Stacked RNN6.实验6.1 训练6.2 结果与分析7.讨论深度学习GRU公式推导1.GRU前向传播2.GRU反向传播GRU代码实现…

关于数学中“函数(function)”的含义

目录 1. 问题 2. “function”是如何翻译成“函数”的&#xff1f; 3. “function”是谁引入数学中的&#xff0c;其意义何在&#xff1f; 3.1 “function”的词源 3.2 “function”引入数学中 3.3 “function”的含义 4. 常见的函数(Common Functions) 4.1 线性函数(L…

word页码如何设置为章节加页码,例如第一章第一页1-1、第二章章第一页2-1

由于用到word页码分章节页码的形式&#xff0c;从网上查了一下&#xff0c;质量真的很差&#xff0c;没有一篇文章讲清楚的&#xff0c;有的所答非所问&#xff0c;一怒之下&#xff0c;利用几个小时的时间解决问题并写下这篇文章&#xff0c;以供大家学习参考&#xff01;&…

【JSP】

文章目录简介Scriptlet脚本小程序JSP的指令标签include静态包含include动态包含不传参传参JSP的四大域对象四种属性范围验证属性范围的特点EL表达式操作字符串操作集合emptyJSTL条件动作标签if标签choose、when 和 otherwise 标签迭代标签foreach格式化动作标签formatNumber标签…

NProgress 进度条的使用方法

安装NProgress 进度条 npm install nprogress --save-dev 在vue项目中mian.js中或router.js或axios.js&#xff1a; import NProgress from nprogress import nprogress/nprogress.css 使用NProgress进度条 NProgress.start(); &#xff1a;进度条开始&#xff1b; NProgr…

GCD和LCM

目录 一 整除 定义 性质 二 GCD 1&#xff09;定义 2&#xff09;性质 3&#xff09;GCD编程 ①暴力法 ②欧几里得算法 ③更相减损术 ④Stein算法 三 LCM ①暴力法 ②最大公约数法 四 裴蜀定理 例题&#xff1a;裴蜀定理 一 整除 定义 a 能整除b,记为 a|b。其…

你还会想起这道题吗

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 原神是由米哈游自主研发的一款全新开放世界冒险游戏。游戏发生在一个被称作「提瓦特」的幻想世界&#xff0c;在这里&#xff0c;被神选中的人将被授予「神之眼」&#xff0c;导引元…

m基于神经网络的气候预测matlab仿真,气候数据采用的BoM气候数据,神经网络为matlab编程实现不使用工具箱函数

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 人工神经网络&#xff08;Artificial Neural Networks&#xff0c;简写为ANNs&#xff09;也简称为神经网络&#xff08;NNs&#xff09;或称作连接模型&#xff08;Connection Model&#xff09…

UICollectionView 实际使用

一. 使用UICollectionView制作书架 我想的书架是那种每一排都可以滑动的。暂时的想法是使用两个collectionView&#xff0c;第一个collectionView布置书架的每一排&#xff0c;第二个布置每一排内部的书。 布置外部的colletionView&#xff0c;这部分很简单&#xff0c;item的…

[ 数据结构 -- 手撕排序算法第五篇 ] 堆排序

文章目录前言一、常见的排序算法二、堆的概念及结构三、堆的实现3.1 堆的插入3.2 堆的删除四、堆排序4.1 向上调整建堆4.2 向下调整建堆4.3 建堆的时间复杂度4.4 堆排序五、堆排序的特性前言 手撕排序算法第五篇&#xff1a;堆排序&#xff01; 从本篇文章开始&#xff0c;我会…

Java+JSP超市管理系统(含源码+论文+答辩PPT等)

项目功能简介: 该项目采用的技术后台框架&#xff1a;Servlet、JSP、JDBC、UI界面&#xff1a;BootStrap、jQuery、数据库&#xff1a;MySQL 系统功能 该系统共包含两种角色&#xff1a;员工和管理员。系统的主要功能模块如下&#xff1a; 1.系统管理 系统登陆、系统退出、修改…

《Mysql是怎样运行的》补充

19 第19章 从猫爷被杀说起-事务简介 19.1 事务的起源 19.1.1 原子性&#xff08;Atomicity&#xff09; 19.1.2 隔离性&#xff08;Isolation&#xff09; 其它的状态转换不会影响到本次状态转换&#xff0c;这个规则被称之为 隔离性 19.1.3 一致性&#xff08;Consisten…

[ISITDTU 2019]EasyPHP rce替换字母

<?php highlight_file(__FILE__);$_ $_GET[_]; if ( preg_match(/[\x00- 0-9\"$&.,|[{_defgops\x7F]/i, $_) )die(ros will not do it);if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )die(you are so close, omg);eval($_); ?> 打开界面有两个i…

Mysql分布式锁(四)乐观锁实现并发

文章目录CAS - Compare And Swap业务改造1. 表结构新增version列2. 修改代码3. 测试问题1. 高并发情况下&#xff0c;性能极低2. ABA问题3. 读写分离情况下导致乐观锁不可靠CAS - Compare And Swap 先比较再交换&#xff0c;一般通过时间戳或者version版本号。 举例&#xff1…

【审计思路】如何快速定位SQLMS注入漏洞?

0x00 前言 MCMS是政府、教育等其他行业常用的CMS&#xff0c;应用广泛&#xff0c;但是底层的代码中仍然遗留不少的问题。这篇文章主要针对SQL注入进行审计并探讨如何快速定位SQL注入漏洞&#xff0c;以及其他工具的应用。 MCMS&#xff0c;是完整开源的Java CMS&#xff01;基…

[ vulhub漏洞复现篇 ] Apache Airflow Celery 消息中间件命令执行漏洞复现 CVE-2020-11981

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

基于asp.net企业网上办公自动化系统-计算机毕业设计

企业网上办公自动化通过对各办公自动化要素的闭环整合&#xff0c;实现了工作流、信息流、知识流和办公自动化系统的整合管理&#xff0c;提供了一个科学、开放、先进的信息化办公平台&#xff0c;实现办公自动化&#xff0c;并进行远程办公或在家办公。企业网上办公自动化将人…