Spring提供了@Value注解帮助我们注入一个属性或者对象,一般情况下它都可以正常工作。
但是在某些特殊的情况下,它可能会产生一些意想不到的错误。
问题复现
新建一个Spring boot应用,
在application.properties中配置以下属性:
spring.application.name=demo2023
server.port=8080
user.name=shishan
user.password=123456
再定义一个controller,
@RestController
@RequestMapping(value = "/demo")
public class DemoController {
@Value("${user.name}")
private String username;
@Value("${user.password}")
private String password;
@RequestMapping(value = "/hello")
public String hello(){
return "username:" + username + ",password:" + password;
}
}
我们访问http://localhost:8080/demo/hello,正常来说应该返回配置的username和password属性值。
然而结果可能和我们想象的不太一致。
如上图所示,返回的结果中,password返回了预期的123456,但是username却返回了“cc”。
我们明明定义的是“user.name=shishan”,怎么返回了一个“cc”呢?
而且“cc”看着很熟悉,好像是我电脑的用户名?
如果有遇到过这个bug的朋友此时可能已经反应过来了,没错,我们定义的user.name 被系统变量覆盖了。
先说结论吧:
当我们使用@Value时,Spring会按照系统环境变量、自定义变量的顺序,依次匹配属性值,当属性值一旦被匹配就立即返回结果,不会继续匹配。
原理分析
@Value实际上的工作就是替换占位符,其实现主要分为3步,
1,判断属性字段上是否有@Value;
2,解析@Value字段值;
3,将结果转化;
现在问题明显出现在第二步,我们debug源码看一下。
对@Value的解析入口在DefaultListableBeanFactory类的doResolveDependency方法
点进resolveEmbeddedValue,一直debug步进,最终可以发现,采用的是 PropertySourcesPlaceholderConfigurer的PropertySources属性替换。最终可以在PropertyPlaceholderHelper类的parseStringValue方法里面找到要查找的属性列表。
上图我们可以看到,我们定义的application.properties排在了第7位,在前面还会查找系统变量。
而我们定义的user.name 正好与系统变量冲突了,所以误打误撞之下,我们定义的值被系统变量覆盖了。
既然知道了原因,我们做一下调整,将user.name=shishan改成username=shishan再试一试。
spring.application.name=demo2023
server.port=8080
username=shishan
userpassword=123456
再次请求http://localhost:8080/demo/hello,发现此时结果可以正常返回
总结
我们非常方便地使用依赖注入的特性时,也需要思考对象从哪里来,是如何创建的,以及注入的结果是否与预期一致。虽然框架简化了我们的开发步骤,但是还是建议大家尽可能地深入理解框架的底层细节。
�
学习技术,分享技术,期待与大家共同进步,也感谢您的点赞与关注。