1. 概述
在本文中,我们将介绍IoC
(Invention of Control
- 控制反转)和DI
(Dependency Injection
- 依赖注入)的概念,然后我们将看看这些如何在Spring框架中实现
2. 什么是控制反转?
控制反转是软件工程中的一个原则,该原则就是将对象或程序的某些部分的控制权转移到容器或框架。它最常用于面向对象编程的环境中。
与我们自定义代码调用库的传统编程相比,IoC
能使框架控制程序流程并调用我们的自定义代码。To enable this, frameworks use abstractions with additional behavior built in
。如果我们想添加自己的行为,我们需要扩展框架的类或者插入我们自己的类。
这种架构的优点是:
- 将任务的执行从其实现中解耦出来
- 使其更容易在不同的实现之间切换
- 程序的模块化程度更高
- 通过隔离组件或模拟其依赖性并允许组件通过约定进行通信可更容易地测试程序
控制反转可以通过各种机制来实现,如策略设计模式(Strategy design pattern
),服务定位器模式(Service Locator Pattern
),工厂模式(Factory pattern
)和依赖注入(DI
)。
3.什么是依赖注入?
依赖注入是实现IoC
的一种模式,其中被颠倒的控制是对象依赖关系的设置。
连接对象与其他对象或将对象“注入”其他对象的操作由an assembler
完成,而不是由对象本身完成。
以下是您如何在传统编程中创建对象依赖关系的方法:
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}
在上面的例子中,我们需要在Store
类本身内实例化Item
接口的实现。
通过使用DI
,我们可以重写示例,而不必指定我们想要的Item
的实现:
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
在接下来的部分中,我们将看到我们如何通过元数据提供Item
的实现。
IoC
和DI
都是简单的概念,但对于我们构建系统的方式有着深刻的影响,所以它们非常值得理解。
4. Spring IoC
容器
IoC
容器是实现IoC
的框架的共同特征。
在Spring
框架中,IoC
容器由接口ApplicationContext
表示。Spring
容器负责实例化,配置和组装对象,这些对象被称为beans
,并管理它们的生命周期。
Spring
框架提供了几个ApplicationContext
接口的实现 — 用于独立应用程序的ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
,以及用于Web
应用程序的WebApplicationContext
。
为了组装beans
,容器使用配置元数据,可以采用XML
配置或注解的形式。
以下是手动实例化容器的一种方法:
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
5. Spring中的依赖注入
要在上面的示例中设置item
属性,我们可以使用元数据。然后,容器将读取这个元数据并在运行时使用它来组装beans
。Spring
中的依赖注入可以通过constructors
,setters
或fields
来完成。
5.1 基于Constructor的依赖注入
在基于Constructor
的依赖注入的情况下,容器将调用一个构造函数,该构造函数的参数代表我们想要设置的依赖项
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
作用域的bean
,Spring
首先检查一个已经存在的bean
的缓存实例是否已经存在,并且只有在没有时才创建一个新实例。
如果我们使用prototype
作用域,容器为每个方法调用返回一个新的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>
5.2 基于Setter的依赖注入
对于基于Setter
的依赖注入,容器将在调用无参数构造函数或无参数静态工厂方法来实例化bean
之后,调用我们类的setter
方法。
我们使用注解创建此配置:
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}
我们也可以将XML
用于相同的bean
配置:
同一个bean
可以组合基于constructor
和基于setter
的注入类型。Spring
文档建议使用基于constructor
的注入来实现强制的依赖关系,以及基于setter
的注入来实现可选的注入。
5.3. 基于Field的依赖注入
在基于Field
的依赖注入中,我们可以通过使用@Autowired
注解标记它们来注入依赖关系:
public class Store {
@Autowired
private Item item;
}
在构造Store
对象时,如果没有构造函数或setter
方法来注入Item
bean
,容器将使用反射将Item
注入Store
我们也可以使用XML
配置来实现这一点。
这种方法看起来更简单,更清洁,但不推荐使用,因为它有一些缺点,例如:
- 此方法使用反射来注入依赖关系,这比基于
constructor
或基于setter
的注入成本高 - 使用这种方法继续添加多个依赖关系非常简单。如果您使用的
constructor
注入具有多个参数,那么我们会认为该类做了不止一件事情可能会违反单一责任原则。(意思大致是添加多个依赖关系简单是简单,但是这样是不是违反了单一责任原则)
5.4 自动装配依赖关系
[Wiring](http://www.baeldung.com/spring-annotations-resource-inject-autowire)
允许Spring
容器通过检查已定义的bean
来自动解决与将要合作bean
之间的依赖关系。
这里有四种使用XML
配置自动装配bean
的模式:
no
:默认值 - 这意味着没有自动装配用于bean
,我们必须明确命名依赖关系。byName
:autowiring
是基于属性的名称完成的,因此Spring
将查找与需要设置的属性同名的bean
。byType
:与byName
自动装配类似,仅基于属性的类型。这意味着Spring
将查找与要设置的属性类型相同的bean
。如果该类型有多个bean
,则该框架会引发异常。constructor
:autowiring
基于constructor
参数完成,这意味着Spring
将查找与constructor
参数具有相同类型的bean。
例如,让我们按照类型自动装入上面定义的item1
bean
到Store
bean
中:
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item){
this.item = item;
}
}
我们也可以使用@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
将一个名为item
的bean
注入到store
bean
的item
属性中:
<bean id="item" class="org.baeldung.store.ItemImpl1" />
<bean id="store" class="org.baeldung.store.Store" autowire="byName">
</bean>
我们也可以通过contructor
参数或setter
明确定义依赖关系来覆盖自动装配。
5.5. Beans的懒加载
默认情况下,容器在初始化期间创建并配置所有的singleton
beans
。为了避免这种情况,您可以在bean
配置中使用值为true
的lazy-init
属性:
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />
因此,item1
bean
只会在第一次请求时初始化,而不是在启动时初始化。这样做的好处是更快的初始化时间,但是要权衡的是只有在请求bean
之后才能发现配置错误,这可能在应用程序已经运行几个小时甚至几天之后。
6. 结论
在本文中,我们介绍了控制反转和依赖注入的概念,并在Spring
框架中对它们进行了例证。
您可以在Martin Fowler
的文章中阅读有关这些概念的更多信息:
- Inversion of Control
- Inversion of Control Containers and the Dependency Injection pattern
您可以在Spring Framework参考手册中了解更多关于IoC
和DI
的Spring
实现。
【参考资料】