Spring为什么推荐使用构造器注入
目录
- 前言
- 常见的三种注入方式
- 构造器注入的好处
一. 前言
Spring框架对Java开发的重要性不言而喻,其核心特性就IOC(Inversion of Control)和AOP,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合。刚到实习的公司被安排测试和修改一些BUG,在查看源代码的时候看到公司的代码Controller中使用的注入service的方式和以前自己常用的注入方式不同,所以本着探究到底的心情,上网搜索了一些相关博客,下面是笔者通过总结相关博客来比较一下常见的三种注入方式。
二. 常见的三种注入方式
这里我们只用注解的方式进行注入(嘿嘿,现在估计大家很少用xml了,所以这里就用注解的方式了)
2.1 field注入方式
@Controller
public class FooController {
@Autowired
private UserService userService;
public List<user> listuser() {
return userService.list();
}
}
这种注入方式是笔者之前做常用的注入方式,原因很简单因为笔者很懒,这种注入方式:
- 注入方式简单:只需要加入要注入的字段,附上
@Autowired
,即可完成。 - 整体代码简洁明了,美观大方。
2.2 构造器注入方式
@Controller
public class FooController {
private final UserService userService;
@Autowired
public FooController(UserService userService) {
this.userService = userService;
}
}
在Spring4.x版本中推荐的注入方式就是这种,相较于上面的field注入方式而言,就显得有点难看,特别是当注入的依赖很多(4个以上)的时候,就会明显的发现代码显得很臃肿。对于从field注入转过来+有强迫症的码友来说,简直可以说是难受的一批。
2.3 setter注入方式
@Controller
public class FooController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
在Spring3.x刚推出的时候,推荐使用注入的就是这种,写起来麻烦,当初推荐Spring自然也有他的道理,这里我们引用一下Spring当时的原话:
The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.
Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.
本人英文实在不好,六级至今未过,直接借用一下原博主的原话,构造器注入参数太多,显得太笨重,另外setter的方式能够让类在之后重新配置或者重新注入。
然而::真香定律::谁也逃不过后面Spring为什么又换成构造器注入了呢?
三. 构造器注入的好处
首先我们来看一下Spring官方文档里是怎么说的:
The Spring team generally advocates constructor injection as it enables one to implement application components asimmutable objectsand to ensure that required dependencies are
not null
. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
简单的翻译一下:这个构造器注入的方式啊,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
下面来简单的解释一下:
- 依赖不可变:其实说的就是final关键字,这里不再多解释了。不明白的园友可以回去看看Java语法。
- 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。所以保证不会为空,Spring总不至于传一个null进去吧 😦
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态。
比较完了setter注入与构造器注入的优缺点,你还没用说使用field注入与构造器的比较呢!那么我们再回头看一看使用最多的field注入方式:
//承接上面field注入的代码,假如客户端代码使用下面的调用(或者再Junit测试中使用)
//这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。
FooController fooController = new FooController();
fooController.listFoo(); // -> NullPointerException
如果使用field注入,缺点显而易见,**对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类**。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。
还值得一提另外一点是:使用field注入可能会导致循环依赖,即A里面注入B,B里面又注入A:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
**如果使用构造器注入,在spring项目启动的时候,就会抛出BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错**。