什么是依赖注入?
每个开始学习 Spring 框架的人都应该听说过依赖注入,但到底这意味着什么?好吧,不就是去源码吗,让我们看看Spring的文档:
依赖注入 (DI) 是一个过程,对象仅通过构造函数参数、工厂方法的参数或对象实例在构造或从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),它通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。
哇,文档上文字这么这么长!所以让我们翻译一下,DI 是一种软件原则,它将程序对象的控制权转移到容器或框架中,在这种情况下,我们将责任放在 Spring 容器中。那么,使用的优势是什么?
代码使用 DI 原则更清晰,并且在为对象提供依赖项时解耦更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更容易测试,特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
“好吧好吧,但我还是不明白这一切的要点,请你说得更清楚些?” 当然,我们的目标是如何在代码中使用他,对吧?因此,让我们看一下这是如何在代码上工作的。
以下是我们如何在传统编程中创建对象依赖关系:
代码语言:javascript复制public class Store {
private Item item;
public Store() {
item = new StoreImpl1();
}
}
在上面的示例中,我们需要在 Store 类本身内实例化 Item 接口的实现,即我们的职责。
通过使用 DI,我们可以重写示例,而无需指定我们想要的 Item 的实现,这种情况下容器会为我们提供一个实现,也就是 Spring 的职责:
代码语言:javascript复制public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
所以,我想现在你对 DI 更加清楚了,让我们来看看如何使用。基本,Di存在两种方式:Constructor-based dependency injection和Setter-based dependency injection。
基于构造函数的依赖注入
在基于构造函数的依赖注入的情况下,容器将调用一个构造函数,每个参数代表我们要设置的依赖项。以下示例显示了一个只能通过构造函数注入进行依赖注入的类:
代码语言:javascript复制public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
基于 Setter 的依赖注入
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数静态工厂方法实例化 bean 后调用 bean 上的 setter 方法来实现的。
代码语言:javascript复制public class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
}
那么,哪种方式更好呢?好吧,建议您使用构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。Setter 注入应该主要只用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。
此外,您应该在一些教程或代码中了解过其他非常用于 DI 的类型,即 Field Injection,让我们看一下它是如何实现的:
代码语言:javascript复制public class Car {
@Autowired
private Engine engine;
//Constructor, getters, setters...
}
Spring 团队不鼓励使用这种方法,部分原因如下:
- 字段注入好用,我们有意无意地引入了很多依赖,而当注入过多的依赖意味着类承担了过多的责任,违反了面向对象的单一职责原则,再多也没有警告被引入,因为这种方法可以无限期地扩展。
- 字段注入对单元测试不友好,必须使用Spring IoC容器来创建这些bean(和IoC容器强耦合),但是原则上单元测试要快,启动IoC容器太慢,如果是构造注入,我们可以把bean当作一个普通的类来创建对象,直接通过构造就可以了。