一、前言
Spring框架对Java开发的重要性不言而喻,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合。前几天的时候,朋友问我为什么要使用构造器的注入方式,后面抽时间了解了一下,下面就是笔者要讨论的就是其注入方式。
二、常见的三种注入方式
2.1 field注入
@Controller
public class FooController {
@Autowired
//@Inject
private FooService fooService;
//简单的使用例子,下同
public List<Foo> listFoo() {
return fooService.list();
}
}
这种注入方式应该是笔者目前为止开发中见到的最常见的注入方式。原因很简单:
- 注入方式非常简单:加入要注入的字段,附上
@Autowired
,即可完成。 - 使得整体代码简洁明了,看起来美观大方。
2.2 构造器注入
@Controller
public class FooController {
private final FooService fooService;
@Autowired
public FooController(FooService fooService) {
this.fooService = fooService;
}
//使用方式上同,略
}
2.3 setter注入
@Controller
public class FooController {
private FooService fooService;
//使用方式上同,略
@Autowired
public void setFooService(FooService fooService) {
this.fooService = fooService;
}
}
三、构造器注入的好处
- 依赖不可变:其实说的就是final关键字,这里不再多解释了。不明白的园友可以回去看看Java语法。
- 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。所以保证不会为空,Spring总不至于传一个null进去吧 😦
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态。
//承接上面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的时候才会报错。