@Autowired 的 "陷阱"

in 工作记录 with 0 comment

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

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

而是用了有参构造器

image.png

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

image.png

也查阅了一些文章
结论:
@Autowired如果字段很少,通常应该使用构造函数注入(从 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