聊
看标题是不是吓一跳,用了好多年的@Autowired用错了吗?没那么夸张,本篇仅仅是讨论一下我们Spring中最常用的依赖注入方式,目前注入方式有三种,分别是:构造函数注入、方法注入、属性注入。我们来看一小段代码
public class HelloController {
@Autowired
private BeanA a;
private BeanB b;
@Autowired
public void setB(BeanB b) {
this.b = b;
}
private final BeanC c;
// @Autowired
public HelloController(BeanC c) {
this.c = c;
}
}
是不是非常熟悉,让我猜一下,大多数人用的是哪一种,我猜肯定是第一种属性注入方式,因为我在学Java刚入门时也一直在用第一种。那为什么标题说你还在用@Autowired吗,是不需要了?还是有替代方案了,还是什么。如果还有不知道@Autowired是作啥的同学请自己科普一下,其实在我写这篇文章之前,我也不知道@Autowired
的详情,我再提两个注解,@Required
@Inject
,有没有同学用过,我自学Java时候刚刚接触Spring时还是用的Required,当年记得还讨论过 用@Autowired 还是@Required,也查询过资料,但那时没有记录的习惯,这也不是本篇讨论的重点。那重点是啥,现在如果不用@Autowired用什么,为什么不用了,有什么区别。读完本篇文章就可以学到。
学
还是先看上面的那段代码,有没有发现第三种方法构造函数注入方式的@Autowired是被注释掉的,会不会有疑问,注释掉之后注入方法还能成功吗?这个问题,或者说这类问题,我个人学习时都会有比较明确的方法,就是《官方文档》,我们一起看一下Spring Boot 2.0的
17. Spring Bean和依赖注入
您可以自由使用任何标准的Spring Framework技术来定义bean及其注入的依赖项。为简单起见,我们经常发现使用
@ComponentScan
(查找您的bean)和使用@Autowired
(进行构造函数注入)效果很好。如果按照上面的建议构造代码(将应用程序类放在根包中),则可以添加
@ComponentScan
任何参数。您的所有应用程序组件(的@Component
,@Service
,@Repository
,@Controller
等)自动注册为春豆。以下示例显示了一个
@Service
使用构造函数注入来获取所需RiskAssessor
Bean的Bean:package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class DatabaseAccountService implements AccountService { private final RiskAssessor riskAssessor; @Autowired public DatabaseAccountService(RiskAssessor riskAssessor) { this.riskAssessor = riskAssessor; } // ... }
如果bean具有一个构造函数,则可以省略
@Autowired
,如以下示例所示:@Service public class DatabaseAccountService implements AccountService { private final RiskAssessor riskAssessor; public DatabaseAccountService(RiskAssessor riskAssessor) { this.riskAssessor = riskAssessor; } // ... }
请注意,使用构造函数注入如何将该
riskAssessor
字段标记为final
,指示该字段 随后无法更改。
也就是官方推荐的使用方式目前是@Autowired注解的构造函数注入,并且@Autowired可以省略。不知道大家有没有写过非Spring框架的Java程序,如果有,可以回忆一下,构造函数用的多吗?为什么用?这个问题留给小伙伴们自己探索。我们继续讨论,为什么要有三种注入方法,什么时候用,优缺点是什么。
记
为了探索这个问题,我在网上科普了一下相关资料,首先要搞明白问题,就需要从根源出发,有人喜欢看源码,搞清楚原理,而我喜欢看文档,关于这件事,我第一个疑问就是@Autowired是何时出现的,从Spring1.0开始翻,2.0->3.0->2.5,终于让我在Spring2.5的文档中找到了他的出生信息。
从Spring 2.5开始,您可以依赖
BeanFactory的
自动装配 作为实现BeanFactoryAware
接口的另一种选择。现在,“传统”构造函数
和byType
自动装配模式(如第3.3.5节“自动装配协作器”一节中所述)能够分别为构造函数参数或setter方法参数提供BeanFactory
类型的依赖项 。为了获得更大的灵活性(包括自动装配字段和使用多个参数方法的能力),请考虑使用新的基于注释的自动装配功能。在这种情况下,BeanFactory
只要有问题的字段,构造函数或方法带有@Autowired
批注,它将自动连接到期望BeanFactory
类型 的字段,构造函数参数或方法参数。有关更多信息,请参见标题为第3.10.1节“ @Autowired”的部分。
而且最早出现时,@Autowired并不支持属性注入方式,那没有@Autowired之前就是另外两种,并且1.0时官方推荐的是Setter方法注入,到了2.0官方就一直是推荐构造函数注入了。有兴趣的同学可以看一卡这篇文章,较早版本的就不再讨论了。
https://spring.io/blog/2007/07/11/setter-injection-versus-constructor-injection-and-the-use-of-required/
继续聊@Autowired,他是从什么时候开始大量使用的,Spring3.0,这时Spring支持了@Autowired给属性使用,但官方并没有推荐使用,到了Spring4.3时,官方又对构造函数进行了说明,当一个类只有一个构造函数时,可以省略@Autowired的注释。那么为什么@Autowired 注解属性的形势还会一直存在并延续至今呢,我通过与朋友讨论时获知了一部分原因,经过写代码尝试和网络上的科普最终汇总了几种情况
@Autowired 属性注入形式优点:
1、可以在单例模式下允许循环依赖注入的存在,即,解决了循环依赖注入的问题。如果有业务必须使用循环依赖注入,可以考虑使用。
2、使用简单直接给属性注解即可,代码量少。
3、使代码整洁,美观。
@Autowired 属性注入形式缺点:
1、不允许声明不可变域,也就是无法对属性进行 final 修饰。
2、容易违反单一职责设计原则,举个例子,一个类使用10个以上的@Autowired,代码看起来并无异常,但一个有10个参数的构造函数,已经违反了一个良好的代码规范,从而考虑是否应该优化该类的使用职责。
3、注入顺序靠后,容易出现空指针调用,举个例子,被注入的类用构造函数给其他属性赋值时,由于注入属性还为被反射注入,造成空指针异常。
4、与依赖注入容器紧密耦合,即,在离开Spring容器外,代码将失去作用,例如单元测试。
5、没有对依赖进行检查
然后再一一分析一下优点,其中比较有争议的就是第一点,我个人觉得第一点并不算是优点,循环依赖这种问题,如果没有特殊情况,从架构设计上讲本身就是违反原则的一种情况,应该即时检查并发现错误的存在并排除此类问题(构造器方法,和Setter方法都可以做到而构造器可以在编译时发现,Setter是在运行时发现),不用说肯定是编译时发现更合适。
其他两点都趋向于可读性和美观方面的优点,其实通过同事的启发构造器方式也可以做到同样的效果,比如利用lombok的注解@AllArgsConstructor, 不过这样会隐藏实现,不熟悉的人会造成额外的阅读麻烦,并不推荐。
@AllArgsConstructor
@RestController
public class HelloController {
private final BeanA a;
private final BeanB b;
private final BeanC c;
@GetMapping("/hi")
public String sayHi() {
a.sayHi();
return "Hi";
}
}
总结
其实@Autowired属性注入还是Setter注入或者构造函数注入,在大多数情况下都不会是问题,所以其实根据个人习惯就好,另外想说的就是多关注一下所用技术的官方资料,更新,补丁等问题。最后放出Spring5的原文翻译。
基于构造函数或基于setter的DI?
由于可以混合使用基于构造函数的DI和基于setter的DI,因此,将构造函数用于强制性依赖项,将setter方法或配置方法用于可选性依赖项是一个很好的经验法则。请注意,可以 在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。
Spring团队通常提倡使用构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项
null
。此外,注入构造函数的组件始终以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味,这表明该类可能承担了太多的职责,应进行重构以更好地解决关注点分离问题。Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法可使该类的对象在以后重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。
使用最适合特定班级的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
循环依赖
如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。
例如:类A通过构造函数注入需要类B的实例,而类B通过构造函数注入需要类A的实例。如果您将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并抛出
BeanCurrentlyInCreationException
。一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用setter注入。换句话说,尽管不建议这样做,但是可以使用setter注入配置循环依赖关系。
与典型情况(没有循环依赖关系)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在完全初始化之前被注入另一个bean(经典的“养鸡和养猪”方案)
注意 只有一个每类注释构造函数 可以作为标记要求,但多个非必需的构造函数可以被注解。在这种情况下,每个候选对象都将被考虑在内,Spring将使用 最贪婪的构造函数,该构造函数可以满足其依存关系,即参数数量最多的构造函数。 @Autowired
建议在注释上使用的 必填属性@Required
。将 所需的属性表示不需要属性自动装配的目的,如果它不能自动装配的属性被忽略。@Required
另一方面,它更强大,因为它可以强制执行通过容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。
文献资料
https://docs.spring.io/spring/docs/2.0.0/reference/beans.html#beans-factory-autowire
https://docs.spring.io/spring/docs/2.5.0/reference/beans.html#beans-autowired-annotation
https://docs.spring.io/spring/docs/3.0.0.RELEASE/reference/htmlsingle/#beans-autowired-annotation
https://docs.spring.io/spring/docs/5.3.0-M1/spring-framework-reference/core.html#beans-dependencies
本文由博客群发一文多发等运营工具平台 OpenWrite 发布