今天在写代码的时候遇到这个情况
以前也经常这么写,在需要注入的属性上面直接添加 @Autowired
注解,但是今天注意到 @Autowired 是有警告的:Field Injection is not recommended
,来看一下 IDEA 给的建议:Create Constructor: AuthProvider(UserService userService)
,原来是不建议使用属性注入的方式,而是建议使用 构造器注入
,那么它们之间有什么区别呢?
@Autowired
我们先来看一下熟悉又陌生的 @Autowired
注解,可以说它是我们使用 Spring 框架的控制反转、依赖注入特性中用的最多的注解,@Autowired 是 Spring 官方提供的注解,在需要注入的地方添加这个注解后,Spring 就会自动为我们装配这个 Bean,这里我们首先要知道,@Autowired 是根据 类型
注入的。举个栗子
声明一个接口如下
1 | public interface Flyable { |
再添加两个实现类,并都将其使用 @Component 注解声明为 Bean
1 |
|
1 |
|
这时候我们要根据接口注入实现类,像这样
1 |
|
到这里其实大家应该可以想到是有歧义的,因为同时有两个符合条件的 Bean 分别是 A 和 B,结果是
1 | Field xxx in lab.zlren.xunwu.base.test.Test required a single bean, but 2 were found: |
这里也验证了 @Autowired 是根据类型注入的,否则不会报歧义而应该是找不到
如何解决歧义呢?最简单的方法就是在我们需要的 Bean 上加入 @Primary
注解,这样对应的 Bean 就在同类型的 Bean 中有了更高的优先级,产生歧义的时候由于存在一个优先级更高的 Bean 那么它就会默认被注入
1 |
|
可以想到如果在 B 上也加入 @Primary 则又会报歧义的错误。。
有没有其他的方法呢?在 Java Config 之前,也就是 xml 配置的时代,有一个属性叫做 beanId
,那么现在 beanId 是什么呢?实际上声明为 Bean 的 类名首字母小写
就是这个 Bean 对应的 beanId,beanId 是唯一的,因此通过它注入是不会有歧义的,这里可以有两种方式通过 beanId 去注入
1 属性名称
,这也是比较简单的方式,比如要注入 A,我们可以这样做(首先去掉 A 上面的 @Primary 注解恢复之前的歧义状态)
1 |
|
由于 A 的 beanId 是首字母小写也就是 『a』,所以这里的属性(变量)名直接就是 beanId 就不会有歧义。但是这样一来 A 这个 Bean 如果更改了类名就又会造成注入失败,那么能不能手动指定 beanId 呢?
2 @Qualifier
手动指定,我们也可以通过它手动指定 beanId 来消除歧义
1 |
|
上面提到 Bean 的默认 beanId 是首字母小写,其实这里也可以手动指定,同样也是通过 @Qualifier 注解,这样再配合 @Autowired 处的 @Qualifier 就可以实现无歧义注入
三种注入方式
@Autowired 的 3 种使用方法:
- 属性(Field)注入
- setter 方法(或者其他方法)注入
- 构造器注入
属性注入是最简单、最简洁的注入方式,一般情况下很多同学都这么做,但它无法注入 final 对象,与此同时它和所依赖的容器是强耦合的,无法在容器外使用,无法绕过反射进行实例化,更像是集成测试。由于没有写在构造器参数列表中,实际的依赖被隐藏在外面,使用 Spring 外的容器可能会引起空指针异常
使用 setter 方法注入的属性无法被设置为 final,上面提到的 或其他方法
表明不只是通过 setter 方法,实际上 setter 方法注入的背后参数列表,因此完全可以将 @Autowired 写在一个普通方法上,它会根据参数列表进行 Bean 装配
第三种就是构造器注入,这是最推荐的方式,但就是如果需要注入的依赖比较多的话。。参数列表可能比较长。。但是这也从某种程度上也提醒我们这个类需要依赖这么多东西,是不是干的活太多了?有没有违背单一职责的原则?
推荐
Spring 3 建议 setter 注入,Spring 4 建议构造器注入
结论:如果是 final 类型的就使用构造器注入,否则可以使用 setter 方法注入
至于属性注入,还是不要使用的好