【SpringBoot】@Value 没有注入预期的值
问题复现
- 在装配对象成员属性时,我们常常会使用 @Autowired 来装配。但是,有时候我们也使用 @Value 进行装配。不过这两种注解使用风格不同,使用 @Autowired 一般都不会设置属性值,而 @Value 必须指定一个字符串值,因为其定义做了要求,定义代码如下:
public @interface Value {/*** The actual value expression — for example, <code>#{systemProperties.myProp}</code>.*/String value();}
- 另外在比较这两者的区别时,我们一般都会因为 @Value 常用于 String 类型的装配而误以为 @Value 不能用于非内置对象的装配,实际上这是一个常见的误区。例如,我们可以使用下面这种方式来 Autowired 一个属性成员:
@Value("#{student}") private Student student;
- 其中 student 这个 Bean 定义如下:
@Bean public Student student(){Student student = createStudent(1, "xie");return student; }
- 当然,正如前面提及,我们使用 @Value 更多是用来装配 String,而且它支持多种强大的装配方式,典型的方式参考下面的示例:
//注入正常字符串 @Value("我是字符串") private String text; //注入系统参数、环境变量或者配置文件中的值 @Value("${ip}") private String ip//注入其他Bean属性,其中student为bean的ID,name为其属性 @Value("#{student.name}") private String name;
- 上面我给你简单介绍了 @Value 的强大功能,以及它和 @Autowired 的区别。那么在使用 @Value 时可能会遇到那些错误呢?这里分享一个最为典型的错误,即使用 @Value 可能会注入一个不是预期的值。
- 我们可以模拟一个场景,我们在配置文件 application.properties 配置了这样一个属性:
username=admin password=pass
- 然后我们在一个 Bean 中,分别定义两个属性来引用它们:
@RestController @Slf4j public class ValueTestController {@Value("${username}")private String username;@Value("${password}")private String password;@RequestMapping(path = "user", method = RequestMethod.GET)public String getUser(){return username + ":" + password;}; }
- 当我们去打印上述代码中的 username 和 password 时,我们会发现 password 正确返回了,但是 username 返回的并不是配置文件中指明的 admin,而是运行这段程序的计算机用户名。很明显,使用 @Value 装配的值没有完全符合我们的预期。
案例解析
- 通过分析运行结果,我们可以知道 @Value 的使用方式应该是没有错的,毕竟 password 这个字段装配上了,但是为什么 username 没有生效成正确的值?接下来我们就来具体解析下。
- 我们首先了解下对于 @Value,Spring 是如何根据 @Value 来查询“值”的。我们可以先通过方法 DefaultListableBeanFactory#doResolveDependency 来了解 @Value 的核心工作流程,代码如下:
@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {//省略其他非关键代码Class<?> type = descriptor.getDependencyType();//寻找@ValueObject value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value != null) {if (value instanceof String) {//解析Value值String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);value = evaluateBeanDefinitionString(strVal, bd);}//转化Value解析的结果到装配的类型TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());try {return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());}catch (UnsupportedOperationException ex) {//异常处理}}//省略其他非关键代码}
- 可以看到,@Value 的工作大体分为以下三个核心步骤。
- 寻找 @value
- 在这步中,主要是判断这个属性字段是否标记为 @Value,依据的方法参考 QualifierAnnotationAutowireCandidateResolver#findValue:
@Nullable protected Object findValue(Annotation[] annotationsToSearch) {if (annotationsToSearch.length > 0) { AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);//valueAnnotationType即为@Valueif (attr != null) {return extractValue(attr);}}return null; }
- 在这步中,主要是判断这个属性字段是否标记为 @Value,依据的方法参考 QualifierAnnotationAutowireCandidateResolver#findValue:
- 解析 @Value 的字符串值
- 如果一个字段标记了 @Value,则可以拿到对应的字符串值,然后就可以根据字符串值去做解析,最终解析的结果可能是一个字符串,也可能是一个对象,这取决于字符串怎么写。
- 将解析结果转化为要装配的对象的类型
- 当拿到第二步生成的结果后,我们会发现可能和我们要装配的类型不匹配。假设我们定义的是 UUID,而我们获取的结果是一个字符串,那么这个时候就会根据目标类型来寻找转化器执行转化,字符串到 UUID 的转化实际上发生在 UUIDEditor 中:
public class UUIDEditor extends PropertyEditorSupport {@Overridepublic void setAsText(String text) throws IllegalArgumentException {if (StringUtils.hasText(text)) {//转化操作setValue(UUID.fromString(text.trim()));}else {setValue(null);}}//省略其他非关代码}
- 通过对上面几个关键步骤的解析,我们大体了解了 @Value 的工作流程。结合我们的案例,很明显问题应该发生在第二步,即解析 Value 指定字符串过程,执行过程参考下面的关键代码行:
String strVal = resolveEmbeddedValue((String) value);
- 这里其实是在解析嵌入的值,实际上就是“替换占位符”工作。具体而言,它采用的是 PropertySourcesPlaceholderConfigurer 根据 PropertySources 来替换。不过当使用 ${username} 来获取替换值时,其最终执行的查找并不是局限在 application.property 文件中的。通过调试,我们可以看到下面的这些“源”都是替换依据:
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, ServletContextPropertySource {name='servletContextInitParams'}, PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='applicationConfig: classpath:/application.properties]'}, MapPropertySource {name='devtools'}]
- 而具体的查找执行,我们可以通过下面的代码(PropertySourcesPropertyResolver#getProperty)来获取它的执行方式:
@Nullable protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {if (this.propertySources != null) {for (PropertySource<?> propertySource : this.propertySources) {Object value = propertySource.getProperty(key);if (value != null) {//查到value即退出 return convertValueIfNecessary(value, targetValueType);}}}return null; }
- 从这可以看出,在解析 Value 字符串时,其实是有顺序的(查找的源是存在 CopyOnWriteArrayList 中,在启动时就被有序固定下来),一个一个“源”执行查找,在其中一个源找到后,就可以直接返回了。
- 如果我们查看 systemEnvironment 这个源,会发现刚好有一个 username 和我们是重合的,且值不是 pass。
- 所以,讲到这里,你应该知道问题所在了吧?这是一个误打误撞的例子,刚好系统环境变量(systemEnvironment)中含有同名的配置。实际上,对于系统参数(systemProperties)也是一样的,这些参数或者变量都有很多,如果我们没有意识到它的存在,起了一个同名的字符串作为 @Value 的值,则很容易引发这类问题。
- 当拿到第二步生成的结果后,我们会发现可能和我们要装配的类型不匹配。假设我们定义的是 UUID,而我们获取的结果是一个字符串,那么这个时候就会根据目标类型来寻找转化器执行转化,字符串到 UUID 的转化实际上发生在 UUIDEditor 中:
问题修正
- 针对这个案例,有了源码的剖析,我们就可以很快地找到解决方案了。例如我们可以避免使用同一个名称,具体修改如下:
user.name=admin user.password=pass
- 但是如果我们这么改的话,其实还是不行的。实际上,通过之前的调试方法,我们可以找到类似的原因,在 systemProperties 这个 PropertiesPropertySource 源中刚好存在 user.name,真是无巧不成书。所以命名时,我们一定要注意不仅要避免和环境变量冲突,也要注意避免和系统变量等其他变量冲突,这样才能从根本上解决这个问题。
相关文章:
![](https://i-blog.csdnimg.cn/direct/1392a29463334c718404b05820f35467.png)
【SpringBoot】@Value 没有注入预期的值
问题复现 在装配对象成员属性时,我们常常会使用 Autowired 来装配。但是,有时候我们也使用 Value 进行装配。不过这两种注解使用风格不同,使用 Autowired 一般都不会设置属性值,而 Value 必须指定一个字符串值,因为其…...
![](https://i-blog.csdnimg.cn/img_convert/999914bb6f27183fcb8d724a184125c4.png)
【STM32-学习笔记-6-】DMA
文章目录 DMAⅠ、DMA框图Ⅱ、DMA基本结构Ⅲ、不同外设的DMA请求Ⅳ、DMA函数Ⅴ、DMA_InitTypeDef结构体参数①、DMA_PeripheralBaseAddr②、DMA_PeripheralDataSize③、DMA_PeripheralInc④、DMA_MemoryBaseAddr⑤、DMA_MemoryDataSize⑥、DMA_MemoryInc⑦、DMA_DIR⑧、DMA_Buff…...
![](https://www.ngui.cc/images/no-images.jpg)
js实现一个可以自动重链的websocket客户端
class WebSocketClient {constructor(url, callback, options {}) {this.url url; // WebSocket 服务器地址this.options options; // 配置选项(例如重试间隔、最大重试次数等)this.retryInterval options.retryInterval || 1000; // 重试间隔&#…...
![](https://i-blog.csdnimg.cn/direct/1f83243264654573ad5ea433298f0fb7.png)
企业总部和分支通过GRE VPN互通
PC1可以ping通PC2 1、首先按照地址表配置ip地址 2、分别在AR1和AR3上配置nat 3、配置GRE a 创建tunnel接口,并选择tunnel协议为GRE,为隧道创建一个地址,用作互联 b 为隧道配置源地址或者源接口,这里选择源接口;再为…...
![](https://i-blog.csdnimg.cn/direct/195b775d9e0a4e11a909e8f9063c738d.png)
油猴支持阿里云自动登陆插件
遇到的以下问题,都已在脚本中解决: 获取到的元素赋值在页面显示,但是底层的value并没有改写,导致请求就是获取不到数据元素的加载时机不定,尤其是弱网情况下,只靠延迟还是有可能获取不到,且登陆…...
![](https://i-blog.csdnimg.cn/direct/918242775f684d628b7dcf2ee74eb48c.png)
【2024年华为OD机试】(C卷,100分)- 字符串筛选排序 (Java JS PythonC/C++)
一、问题描述 题目描述 输入一个由N个大小写字母组成的字符串 按照ASCII码值从小到大进行排序 查找字符串中第K个最小ASCII码值的字母 (k > 1) 输出该字母所在字符串中的位置索引 (字符串的第一个位置索引为0) k如果大于字符串长度则输出最大ASCII码值的字母所在字符串…...
![](https://www.ngui.cc/images/no-images.jpg)
iOS - runtime总结
详细总结一下 Runtime 的核心内容: 1. 消息发送机制 // 消息发送的基本流程 id objc_msgSend(id self, SEL _cmd, ...) {// 1. 获取 isaClass cls object_getClass(self);// 2. 查找缓存IMP imp cache_getImp(cls, _cmd);if (imp) return imp(self, _cmd, ...);…...
![](https://i-blog.csdnimg.cn/img_convert/0ede82cc25798a0aebfb5d2927a2a678.png)
第33 章 - ES 实战篇 - MySQL 与 Elasticsearch 的一致性问题
思维导图 0. 前言 MySQL 与 Elasticsearch 一致性问题是老生常谈了。网上有太多关于这方面的文章了,但是千篇一律,看了跟没看没有太大区别。 在生产中,我们往往会通过 DTS 工具将 binlog 导入到 Kafka,再通过 Kafka 消费 binlog&…...
![](https://i-blog.csdnimg.cn/direct/59cffa2b60cf45b2a5f38743f4635abf.png#pic_center)
Artec Leo 3D扫描仪与Ray助力野生水生动物法医鉴定【沪敖3D】
挑战:捕获大型水生哺乳动物(如鲸鱼)的数据,搭建全彩3D模型,用于水生野生动物的法医鉴定、研究和保护工作。 解决方案:Artec Eva、Artec Space Spider、Artec Leo、Artec Ray、Artec Studio、CT scans 效果&…...
![](https://www.ngui.cc/images/no-images.jpg)
PythonQT5打包exe线程使用
打包: pyinstaller --noconsole --onefile test.py–noconsole 表示不需要打开命令行 修改:test.spec 一般项目里面需要用的资源文件,比如lib、png、exe等。 需要单独修改spec文件 pathex[.],binaries[(D:/test.png, .),(D:/simsun.ttc, .…...
![](https://i-blog.csdnimg.cn/direct/7aea041960ea4082ae89409024044ca0.png)
【Powershell】Windows大法powershell好(二)
PowerShell基础(二) 声明:该笔记为up主 泷羽的课程笔记,本节链接指路。 警告:本教程仅作学习用途,若有用于非法行为的,概不负责。 1. powershell 执行外部命令 powershell也可以执行一些外部的…...
![](https://www.ngui.cc/images/no-images.jpg)
前端学习-环境this对象以及回调函数(二十七)
目录 前言 目标 环境对象 作用 环境对象this是什么? 判断this指向的粗略规则是什么? 回调函数 目标 常见的使用场景 综合案例:Tab任务栏切换 总结 前言 男儿何不带吴钩,收取关山五十州 目标 能够分析判断函数运行在不…...
![](https://i-blog.csdnimg.cn/direct/4b7cecf72f3d4573a83ea9135498d3fc.png)
Element-plus、Element-ui之Tree 树形控件回显Bug问题。
需求:提交时,需要把选中状态和半选中状态 的数据id提交。如图所示: 数据回显时,会出现代码如下: <template><el-tree ref"treeRef" :data"tree" show-checkbox node-key"id" …...
![](https://i-blog.csdnimg.cn/direct/5d41dfc4f6804a7a8e1d6140fcf4380f.png)
互联网全景消息(10)之Kafka深度剖析(中)
一、深入应用 1.1 SpringBoot集成Kafka 引入对应的依赖。 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupI…...
![](https://www.ngui.cc/images/no-images.jpg)
Oracle Dataguard(主库为双节点集群)配置详解(5):将主库复制到备库并启动同步
Oracle Dataguard(主库为双节点集群)配置详解(5):将主库复制到备库并启动同步 目录 Oracle Dataguard(主库为双节点集群)配置详解(5):将主库复制到备库并启动…...
![](https://www.ngui.cc/images/no-images.jpg)
pytorch小记(一):pytorch矩阵乘法:torch.matmul(x, y)
pytorch小记(一):pytorch矩阵乘法:torch.matmul(x, y)/ x y 代码代码 1:torch.matmul(x, y)输入张量:计算逻辑:输出结果: 代码 2:y y.view(4,1)…...
![](https://i-blog.csdnimg.cn/direct/2ad9f1c7c377479b9e238b31084eeff3.png)
PyTorch环境配置常见报错的解决办法
目标 小白在最基础的环境配置里一般都会出现许多问题。 这里把一些常见的问题分享出来。希望可以节省大家一些时间。 最终目标是可以在cmd虚拟环境里进入jupyter notebook,new的时候有对应的环境,并且可以跑通所有的import code。 第一步:…...
![](https://i-blog.csdnimg.cn/direct/ac5217088e9248c091580b57432bc18d.png)
罗永浩再创业,这次盯上了 AI?
罗永浩,1972年7月9日生于中国延边朝鲜族自治州的一个军人家庭,是一名朝鲜族人;早年在新东方授课,2004年当选 “网络十大红人” ;2006年8月1日,罗永浩创办牛博网;2008年5月,罗永浩注册…...
![](https://www.ngui.cc/images/no-images.jpg)
VUE3 provide 和 inject,跨越多层级组件传递数据
provide 和 inject 是 Vue 3 提供的 API,主要用于实现祖先组件与后代组件之间的依赖注入。它们可以让你在组件树中,跨越多层组件传递数据,而不需要通过 props 或事件的方式逐层传递。这个机制主要用于状态共享、插件系统或某些跨层级的功能。…...
![](https://i-blog.csdnimg.cn/direct/4d1b3cd9fd654020ac56ce780a7d4987.png)
git打补丁
1、应用场景 跨仓库升级 开发项目B使用的是开源项目A。开源项目A发现漏洞,作者进行了修复,我们可以通过使用git补丁的方式,将作者修改的内容复制到我 们的项目B中。 2、TortoiseGit方式 源仓库 格式化补丁 根据提交数量,生成…...
![](https://i-blog.csdnimg.cn/direct/42526c56c9924d3d8edf0eb3db3b1044.jpeg)
机械燃油车知识图谱、知识大纲、知识结构(持续更新...)
一、发动机 曲柄连杆机构 配气机构 点火系统 起动系统 燃油供给系统 润滑系统 冷却系统 二、底盘 (一)传动系统 1、离合器 2、变速器 3、万向传动装置 4、驱动桥 (二)行驶系统 1、车架 2、车桥 3、悬架 4、车轮 &a…...
![](https://www.ngui.cc/images/no-images.jpg)
Vue3学习总结
一、Vue 3 基础搭建与核心语法 1.创建 Vue 3 应用 在项目的入口文件 main.js 中,通过以下代码创建 Vue 3 应用实例: import { createApp } from vue; import App from ./App.vue;const app createApp(App); app.mount(#app); 这几行代码的作用是引入…...
![](https://i-blog.csdnimg.cn/img_convert/9bd84aa6c16544bd68ab12c81ea351ad.webp?x-oss-process=image/format,png)
Type-C双屏显示器方案
在数字化时代,高效的信息处理和视觉体验已成为我们日常生活和工作的关键需求。随着科技的进步,一款结合了便携性和高效视觉输出的设备——双屏便携屏,逐渐崭露头角,成为追求高效工作和娱乐体验人群的新宠。本文将深入探讨双屏便携…...
![](https://i-blog.csdnimg.cn/direct/bd5db2a2310943cf89637f985253a3c3.heic)
【读书与思考】焦虑与内耗
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 导言 今天一个朋友和我说,最近比较焦虑和内耗,无心工作和学习,我问他你焦虑内耗的时候,时间主要花在哪了,他告诉我说主要花在看有关移…...
![](https://www.ngui.cc/images/no-images.jpg)
基于python的网页表格数据下载--转excel
基于 Python 的网页表格数据爬取与下载:以维基百科为例 目录 基于 Python 的网页表格数据爬取与下载:以维基百科为例1. 背景介绍2. 工具与环境3. 操作步骤1. 获取网页内容2. 定位表格元素3. 表格变身 Pandas DataFrame4. 检查数据,收工!5. 进阶玩法与优化6. 完整代码4. 结果…...
![](https://www.ngui.cc/images/no-images.jpg)
Vue.js开发入门:从零开始搭建你的第一个项目
前言 嘿,小伙伴们!今天咱们来聊聊 Vue.js,一个超火的前端框架。如果你是编程小白,别怕,跟着我一步步来,保证你能轻松上手,搭建起属于自己的第一个 Vue 项目。Vue.js 可能听起来有点高大上&#…...
![](https://i-blog.csdnimg.cn/direct/49af320fcb47436c864c359c3a7bc7a5.png)
LS1046+XILINX XDMA PCIE调通
欢迎点赞收藏,欢迎私下讨论技术,分享技术 硬件平台 :NXP LS1046 XILINX FPGA 软件平台:LINUX 4.19.68 buildroot LS1046 PEX3 接 XILINX FPGA,linux使用designware的PCI主控制器。下载XILINX DMA驱动,解…...
![](https://i-blog.csdnimg.cn/direct/7941b86d92c547cfa3bdc17012bfcbd7.png)
HarmonyOS:@LocalBuilder装饰器: 维持组件父子关系
一、前言 当开发者使用Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入LocalBuilder装饰器。…...
![](https://www.ngui.cc/images/no-images.jpg)
YOLOv10-1.1部分代码阅读笔记-downloads.py
downloads.py ultralytics\utils\downloads.py 目录 downloads.py 1.所需的库和模块 2.def is_url(url, checkFalse): 3.def delete_dsstore(path, files_to_delete(".DS_Store", "__MACOSX")): 4.def zip_directory(directory, compressTrue, ex…...
![](https://i-blog.csdnimg.cn/direct/38ea57cdd8e8406390eb6814d86d96a7.png)
计算机图形学【绘制立方体和正六边形】
工具介绍 OpenGL:一个跨语言的图形API,用于渲染2D和3D图形。它提供了绘制图形所需的底层功能。 GLUT:OpenGL的一个工具库,简化了窗口创建、输入处理和其他与图形环境相关的任务。 使用的函数 1. glClear(GL_COLOR_BUFFER_BIT |…...
![](/images/no-images.jpg)
越秀区建网站公司/直播网站排名
我不断听到队友说 “测试不是我的工作,我是<insert_role>”或“设计产品不是我的工作,我是<insert_role>” 如果不加以检查,我会对消息引起的行为感到非常厌倦。 一个团队不仅仅是其各个部分的总和,一个团队具有协…...
![](https://img-blog.csdnimg.cn/2022010710192087526.png)
宁波哪里可以做网站/百度网盘网页版入口官网
1. 表的基本概念 在数据库中,表是一种非常重要的数据库对象,是组成数据库的基本对象,由若干个字段组成,主要用来储存数据记录。 表中的数据库对象包含列,索引和触发器。 列:也称属性列,在具体创建表时必须指定列的名字和数据类型。 索引:是根据指定的数据库表列建立…...
![](/images/no-images.jpg)
白山北京网站建设/推广方案框架
C#下RSA算法的实现(适用于支付宝和易宝支付) 目录(?)[-] RSA算法代码RSA算法测试代码RSA算法代码: [csharp] view plaincopy using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Secur…...
![](/images/no-images.jpg)
洛阳bbs/广州seo怎么做
在多线程应用中,所有的线程都是共享资源,线程时并发运行的,此时,就有可能发导致多个线程同时访问操作共享资源。假如有AB两个线程,A线程读共享资源,B线程写共享资源,就会发生A线程读取的共享资源…...
![](/images/no-images.jpg)
微信做淘宝客网站/百度关键词搜索排名
题目描述 社交网络中我们给每个人定义了一个“活跃度”,现希望根据这个指标把人群分为两大类,即外向型(outgoing,即活跃度高的)和内向型(introverted,即活跃度低的)。要求两类人群的…...
![](/images/no-images.jpg)
戴南做网站/郑州seo优化外包顾问阿亮
高手必读 网络端口安全防护技巧放送众所周知,计算机之间通信是通过端口进行的,例如你访问一个网站时,Windows就会在本机开一个端口(例 如1025端口),然后去连接远方网站服务器的一个端口,别人访问…...