依赖注入可以使用 @Autowired
, @Resource
, @Inject
三个注解,那么这3中注解有何异同呢?
同时在Spring框架中, 当我们在使用依赖注入的时候,通常有三种方式:
通过filed变量来注入
通过setter方法来注入
通过constructor构造器来注入
那么他们有什么区别吗?应该选择哪种方式更好?
当你在使用@Autowired
时,是否有出现过Field injection is not recommended
的警告?你知道这是为什么吗?
Spring 依赖注入有哪几种方式?官方是怎么建议使用的呢?下面我给你详细道来!
@Autowired
为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired
。
例如: 想要注入类型为MyService
的Bean
所谓基于 field 注入,就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。
@Autowired
// @Qualifier("serviceA")
private MyService service;
这是我平常开发中看的最多也是最熟悉的一种方式,同时,也正是 Spring 团队所不推荐的方式。
好处:
这种方式非常的简洁,代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired
扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。
坏处:
基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。
通过对应变量的setXXX()
方法以及在方法上面使用注解,来完成依赖注入。
private MyService service;
@Autowired
// @Qualifier("serviceA")
public void setService(MyService service) {
this.service = service;
}
提示: 在 Spring 4.3 及以后的版本中,setter 上面的
@Autowired
注解是可以不写的。
将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。
private final MyService service;
@Autowired
public MyController(@Qualifier("serviceA") MyService service) {
this.service = service;
}
提示: 在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写
@Autowired
注解。
类型(type)
在上下文中查找匹配的bean查找type为MyService
的beanname
进行匹配如果有@Qualifier
注解,则按照@Qualifier
指定的name
进行匹配查找name为 serviceA 的bean,如果没有,则按照变量名进行匹配查找name为 service 的bean@Autowired(required=false
),如果设置required为false(默认为true),则注入失败时不会抛出异常)基于constructor的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;
基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于constructor注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或无参数构造函数。
相信很多同学都选择使用直接在成员变量上写上注解来注入,正如我们所见,这种方式看起来非常好,精短,可读性高,不需要多余的代码,也方便维护;
当我们利用constructor来注入的时候,假如我们需要注入的对象特别多的时候,我们的构造器就会显得非常的冗余、不好看;
当我们选择setter方法来注入的时候,我们不能将对象设为final的;
当我们在field变量上来实现注入的时候
在使用IDE开发工具进行Spring开发,当你在字段上面使用@Autowired
注解的时候,你会发现会有警告提示:
Field injection is not recommended
不建议使用基于 field 的注入方式。
Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".
Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。
提示: Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着代码出现了垃圾,这个类可能承担了过多的责任,需要重构。
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。
@Resource
是JSR-250定义的注解。Spring 在CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resourc
e。
@Resource 有两个重要的属性:name
和 type
,而Spring 将 @Resource
注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
在Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用 AutowiredAnnotationBeanPostProcessor来处理的。
@Inject是 JSR-330 定义的规范,如果使用这种方式,切换到Guice(Guice 是 google 开源的轻量级 IoC 框架)也是可以的
如果要说两个的区别,首先 @Inject 是Java EE包里的,在SE环境需要单独引入。另一个区别在于 @Autowired 可以设置required=false
而 @Inject 并没有这个属性。