Spring 中的控制反转(IoC)和依赖注入(DI)
1、概览
本文将带你了解 IoC(控制反转)和 DI(依赖注入)的概念,以及如何在 Spring 中实现这些概念。
2、控制反转(IoC)是什么?
控制反转(Inversion of Control)是软件工程中的一项原则,它将对象或程序部分的控制权转移到容器或框架中。在面向对象的编程中经常使用。
也有人叫 “反转控制”。
在传统编程中,我们使用自定义代码来调用库,而 IoC 则使框架能够控制程序的流程,并调用我们的自定义代码。框架使用了带有内置附加行为的抽象类来实现这一点。如果我们想添加自己的行为,就需要继承框架的类或插入自己的类。
这种架构的优势在于:
- 将任务的执行与任务的实现分离开来
- 更容易在不同的实现之间切换
- 程序的模块化程度更高
- 通过隔离组件或模拟其依赖,并允许组件通过 “约定” 进行通信,从而更轻松地测试程序
我们可以通过各种机制实现控制反转,例如:策略模式、服务定位模式、工厂模式和依赖注入(DI)。
3、依赖注入(DI)是什么?
依赖注入是可以用来实现 IoC 的一种模式,其中被反转的控制是 设置对象的依赖。
将对象与其他对象 “装配” 起来,或将对象 “注入” 到其他对象中是由组装器(Assembler)而不是对象本身来完成的。
下面是我们在传统编程中创建依赖对象的方法:
public class Store {
private Item item;
public Store() {
item = new ItemImpl1(); // 实例化所需的依赖
}
}
重写示例,通过使用 DI,而无需指定 Item
的实现:
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
在接下来的章节中,将带你了解如何通过元数据(Metadata)来实现 Item
。
IoC 和 DI 都是简单的概念,但它们对构建系统的方式有着深刻的影响,因此非常值得全面了解。
4、Spring IoC 容器
IoC 容器是实现 IoC 的框架的共同特征。
在 Spring 框架中,ApplicationContext
接口代表 IoC 容器。Spring 容器负责实例化、配置和组装称为 Bean 的对象,并管理它们的生命周期。
Spring 框架提供了多个 ApplicationContext
接口的实现:用于独立应用的 AnnotationConfigApplicationContext
、ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
,以及用于 Web 应用的 WebApplicationContext
。
容器使用配置元数据来组装 Bean,其形式可以是 XML 配置或注解。
下面是一种手动实例化容器的方法:
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
下面是一个使用 AnnotationConfigApplicationContext
手动实例化容器的示例:
AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext();
当你创建 AnnotationConfigApplicationContext
的实例并为其提供一个或多个配置类时,它会扫描这些类以查找 @Bean
注解和其他相关注解。然后,它会初始化和管理这些类中定义的 Bean,设置它们的依赖并管理它们的生命周期。
可以使用元数据设置上例中的 item
属性。然后,容器将读取这些元数据,并在运行时使用它们来组装 Bean。
Spring 中的依赖注入可以通过构造函数、Setter 方法或字段来实现。
5、基于构造函数的依赖注入
在基于构造函数的依赖注入中,容器将调用一个构造函数,其参数分别代表需要设置的依赖。
Spring 主要通过类型、属性名称和参数索引来解析每个参数。
先来看看使用注解对 bean 及其依赖进行配置的过程:
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
@Configuration
注解表示该类是Bean定义的源头。可以将其添加到多个配置类中。
在方法上使用 @Bean
注解来定义 Bean。如果不指定自定义名称,那么 bean 名称默认为方法名称。
对于使用 singleton
Scope(默认)的 Bean,Spring 会首先检查是否已经存在该 Bean 的缓存实例,如果不存在,才会创建一个新实例。如果使用 prototype
Scope,容器会为每个方法调用返回一个新的 Bean
实例。
创建 Bean 配置的另一种方法是通过 XML 配置:
<bean id="item1" class="org.baeldung.store.ItemImpl1" />
<bean id="store" class="org.baeldung.store.Store">
<constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" />
</bean>
6、基于 Setter 方法的依赖注入
对于基于 Setter 方法的 DI,容器将在调用无参数构造函数或无参数静态工厂方法实例化 bean 后,调用类的 Setter 方法。
通过注解定义:
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}
也可以使用 XML 来配置相同的 Bean:
<bean id="store" class="org.baeldung.store.Store">
<property name="item" ref="item1" />
</bean>
可以对同一个 Bean 结合使用基于构造函数和基于 Setter 方法的注入方式。Spring 文档建议 对强制依赖使用基于构造函数的注入,而对可选依赖关系使用基于 Setter 方法的注入。
7、基于字段的依赖注入
在基于字段的依赖注入,可以通过 @Autowired
注解来注入依赖:
public class Store {
@Autowired
private Item item;
}
在构造 Store
对象时,如果没有构造函数或 Setter 方法来注入 Item
Bean,容器将使用反射将 Item
注入到 Store
(也可以使用 XML 配置来实现)。
这种方法可能看起来更简单、更干净,但不建议使用,因为它有一些缺点,例如:
- 该方法使用反射来注入依赖,这比基于构造函数或基于 Setter 注入方法的成本更高。
- 使用这种方法很容易会不断地添加多个依赖。如果使用构造函数注入,过多的参数会让我们意识到该类做了不止一件事,这可能会违反 “单一责任原则”。
8、自动装配
通过检查已定义的 Bean,装配(Wiring)允许 Spring 容器自动解析协作 Bean 之间的依赖关系。
使用 XML 配置对 Bean 进行自动装配有四种模式:
no
:默认值,这意味着 Bean 不使用自动装配,我们必须明确命名依赖。byName
:自动装配是根据属性名称完成的,因此 Spring 将查找与需要设置的属性名称相同的 Bean。byType
:与byName
自动装配类似,只是基于属性的类型。这意味着 Spring 将寻找与要设置的属性类型相同的 Bean。如果该类型的 Bean 不只一个,框架就会抛出异常。constructor
:自动装配是根据构造器参数完成的,这意味着 Spring 将寻找与构造器参数类型相同的 Bean。
例如,将上面定义的 item
Bean 按类型自动装配到 store
Bean 中:
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item) {
this.item = item;
}
}
注意,自 Spring 5.1 起,autowrie
属性已被弃用。
还可以使用 @Autowired
注解注入 Bean,以便按类型自动装配:
public class Store {
@Autowired
private Item item;
}
如果有多个相同类型的 Bean,可以使用 @Qualifier
注解通过名称来引用 Bean:
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
通过 XML 配置按类型自动装配 Bean:
<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>
接着,通过 XML 向 store
Bean 的 item
属性注入一个名为 item
的 Bean:
<bean id="item" class="org.baeldung.store.ItemImpl1" />
<bean id="store" class="org.baeldung.store.Store" autowire="byName">
</bean>
还可以通过构造函数参数或 Setter 方法明确定义依赖,从而覆盖自动装配功能。
9、 Bean 的延迟初始化
默认情况下,容器会在初始化过程中创建和配置所有单例(singleton) Bean。为了避免这种情况,我们可以在 Bean 配置中使用值为 true
的 lazy-init
属性:
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />
如上,item1
Bean 只会在首次请求时初始化,而不会在启动时初始化。这样做的好处是初始化时间更快,但代价是在请求 Bean 后才会发现任何配置错误,而这可能是在应序运行数小时甚至数天后。
10、总结
本文介绍了控制反转(IoC)和依赖注入(DI)的概念,并在 Spring 框架中进行了示例演示。
此外,你还可以在 Spring 中文文档 中了解 IoC 和 DI 的 Spring 实现。
Ref:https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring