@Autowired 的 "陷阱"

in 工作记录 with 0 comment

我个人的业务代码经常大量的使用@Autowired,一直没觉得有什么不妥,最近阅读到的一些东西,让我有了新的观点...

最近阅读了Halo,也就是本博客的源码,Halo中有很多注入的地方没有使用@Autowired。

而是用了有参构造器

image.png

Halo的代码我觉得写的还是很漂亮的... 有点好奇Halo为什么这么写,于是仔细看了一直被我无视的Idea的 @Autowired 的建议。

image.png

也查阅了一些文章
结论:
如果字段很少,通常应该使用构造函数注入(从 Spring 4 开始就支持构造函数注入)。

为什么呢?

  1. @Autowired 我们工作中一般都是注入单例对象, @Autowired 设置值的时机是 在构造函数执行后,利用反射注入的,也就是说: 我们无法设置@Autowired注入的对象为 final, 注入的单例就存在被修改的可能。

image.png

  1. 注入单例对象,一般为必须的参数,但是 @Autowired并不能直观的显示出来,也没有任何提示,甚至可能出现必须的注入单例对象为null的现象. (这点我甚至生产环境中遇到过)。

  2. 如2所示,有参构造,编写测试类时候会更加容易,而且@Autowired没有反射或 Spring 容器就无法进行单元测试了。 (中枪了! 减少了测试依赖,轻易进行部分的单元测试,感觉这点很友好)。

这些让我想起了一次严重的生产事故!

起因是一个抽象父类的有个 @Autowired 注入的参数值,这个值负责控制 一些线程的超时无响应时候的自动唤醒时间,子类都为@Component的单例对象, 这些看上去一切都正常。

但是有位人员错误的使用了子类,自行的 New 的方式创建了子类对象进行操作,父抽象类中的值并未注入成功,但是却没有任何的提示。

开发人员也没有意识到错误,因为这些线程只会在网络不畅时候发生死锁(失去了超时自动唤醒),上线后部分服务网络不畅时,导致了经常的线程池耗尽, 引发的事故。

我当时用了将近一个星期来排查这个问题,一点一点的Debug, 模拟线上环境 才找到了死锁的位置。

当时只觉得他没有按照规范去开发,完全没有意识到自己的问题。

现在想起来,有参构造而非 @Autowired, 在构建对象时就有显式的提示必要的值,就可以避免这个问题,谁都有犯错的时候,人多了难免有低级错误, 完全依赖默认的规矩也是不可靠的..

参考文章:

https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.auto-configuration
https://kknews.cc/zh-sg/news/ez8j3g4.html
https://stackoverflow.com/questions/62845494/autowired-says-field-injection-not-recommended