1.概述
我们将介绍IoC(控制反转)和DI(依赖注入)的概念,
然后看看如何使用Spring框架去实现控制反转。
2.什么是控制反转?
控制反转是软件工程中的一个原则,通过该原则,对象或程序的一部分的控制被转移到容器或框架。
它最常用于面向对象编程的上下文中。
与我们的自定义代码调用库的传统编程相比,控制反转使框架能够控制程序流程并调用我们的自定义代码。
为了实现这一点,框架使用内置额外行为的抽象。
如果我们想要添加自己的行为,我们需要扩展框架的类或插入我们自己的类。
这种架构的优点是:
- 将任务的执行与其实现分离
- 使得在不同的实现之间切换更容易
- 更高程度的模块化
- 通过隔离组件或模拟其依赖关系并允许组件通过约定进行通信来更轻松地测试程序
控制反转可以通过各种机制实现,例如:策略设计模式,服务定位模式,工厂模式和依赖注入(DI)。
我们这里只看依赖注入。
3. 什么是依赖注入?
依赖注入是一种实现IoC的模式,其中被反转的控件是对象依赖项的设置。
将对象与其他对象连接或将对象“注入”其他对象的行为由容器或框架,而不是对象本身完成。
以下是在传统编程中创建对象依赖项的方法:
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容器负责实例化,配置和组装称为bean的对象,以及管理它们的生命周期。
Spring框架提供了ApplicationContext接口的几个实现:
- 用于独立应用程序的ClassPathXmlApplicationContext和FileSystemXmlApplicationContext
- 用于Web应用程序的WebApplicationContext
为了组装bean,IoC容器使用的配置元数据可以采用XML配置或注释的形式。
下面是手动实例化容器的一种方法:
ApplicationContext context =
new AnnotationConfigApplicationContext(ConfigurationDemo.class);
要在上面的示例中设置item属性,我们可以使用元数据。然后,容器将读取此元数据并使用它在运行时组装bean。
Spring中的依赖注入可以通过构造函数,setter方法或字段来完成。
5.基于构造函数的依赖注入
在基于构造函数的依赖项注入的情况下,容器将调用带有参数的构造函数,每个参数表示我们要设置的依赖项。
Spring主要按类型解析每个参数,后跟属性的名称和消歧的索引。让我们使用注解来查看bean及其依赖项的配置:
@Bean
public ConstructorDemo constructorDemo() {
return new ConstructorDemo();
}
@Bean
public Paramter paramter() {
return new Paramter();
}
@Bean
public ConstructorWithParamterDemo constructorWithParamterDemo() {
return new ConstructorWithParamterDemo(paramter());
}
@Configuration注释表明类是bean定义的来源。此外,我们可以将其添加到多个配置类.
@Bean注释用于上的方法来定义的bean。如果我们不指定自定义名称,则bean名称将默认为方法名称
对于bean来说默认的scope是单列模式(singleton)。
Spring首先检查bean的缓存实例是否已经存在,如果不存在则仅创建新实例。
如果我们使用prototype范围,容器会为每个方法调用返回一个新的bean实例。
创建bean配置的另一种方法是通过XML配置:
<bean id="beanDemo"
class="com.ripjava.springdi.BeanDemo">
</bean>
<bean id="constructorDemo"
class="com.ripjava.springdi.ConstructorDemo">
</bean>
<bean id="paramter" class="com.ripjava.springdi.Paramter">
</bean>
<bean id="ConstructorWithParamterDemo"
class="com.ripjava.springdi.ConstructorWithParamterDemo">
<constructor-arg ref="paramter" />
</bean>
6.基于setter的依赖注入
对于基于setter的DI,在调用无参构造函数或无参数静态工厂方法来实例化bean之后,容器将调用我们类的setter方法。让我们使用注释创建此配置:
@Bean
public ConstructorWithParamterDemo constructorWithParamterDemo2() {
ConstructorWithParamterDemo demo = new ConstructorWithParamterDemo();
demo.setParamter(paramter());
return demo;
}
我们也可以将XML用于相同的bean配置:
<bean id="ConstructorWithParamterDemo"
class="com.ripjava.springdi.ConstructorWithParamterDemo">
<property name="paramter" ref="paramter" />
</bean>
7.基于字段的依赖注入
在基于字段的DI的情况下,我们可以通过使用*@Autowired*注释标记它们来注入依赖项:
public class ConstructorWithParamterDemo {
@Autowired
private Paramter paramter;
}
在构造ConstructorWithParamterDemo对象时,如果没有构造函数或setter方法来注入 paramterbean,容器将使用反射将paramter注入。
在构造ConstructorWithParamterDemo对象时,如果没有构造函数或setter方法来注入 paramterbean,容器将使用反射将paramter注入ConstructorWithParamterDemo。
这种方法可能看起来更简单,更清晰,但不推荐使用,因为它有一些缺点,例如:
- 此方法使用反射来注入依赖项,这比基于构造函数或基于setter的注入更昂贵
- 使用这种方法继续添加多个依赖项非常容易。如果你正在使用具有多个参数的构造函数注入,那么我们会认为该类违反了单一责任原则。
8.自动装配依赖关系
自动注入允许Spring容器通过检查已定义的bean来自动解决协作bean之间的依赖关系。
使用XML配置有四种自动装配bean的模式:
- no:默认值 - 这意味着没有为bean使用自动装配,我们必须明确命名依赖项
- byName:自动装配是基于属性的名称完成的,因此Spring将查找与需要设置的属性同名的bean
- byType:类似于 byName自动装配,仅基于属性的类型。这意味着Spring将查找具有相同类型属性的bean。如果存在多个该类型的bean,则框架会抛出异常。
- 构造函数:自动装配是基于构造函数参数完成的,这意味着Spring将查找与构造函数参数具有相同类型的bean
例如,让我们按类型将上面定义的paramter bean 自动装入ConstructorWithParamterDemo bean:
我们还可以使用*@Autowired*注释注入bean以按类型自动装配:
public class ConstructorWithParamterDemo {
@Autowired
private Paramter paramter;
}
如果有多个相同类型的bean,我们可以使用*@Qualifier*批注按名称引用bean:
public class ConstructorWithParamterDemo {
@Autowired
@Qualifier("paramter1")
private Paramter paramter;
}
现在,让我们通过XML配置按类型自动装配bean:
<bean id="ConstructorWithParamterDemo"
class="com.ripjava.springdi.ConstructorWithParamterDemo" autowire="byType">
</bean>
接下来,让我们通过XML 将名为paramter的bean注入到ConstructorWithParamterDemobean 的paramter属性中:
<bean id="ConstructorWithParamterDemo"
class="com.ripjava.springdi.ConstructorWithParamterDemo" autowire="byName">
</bean>
我们还可以通过构造函数参数或setter显式定义依赖关系来覆盖自动装配。
9.懒惰的初始化
默认情况下,容器在初始化期间创建并配置所有单例bean。要避免这种情况,可以在bean配置中使用值为true的lazy-init属性:
因此,paramter bean只在首次请求时初始化,而不是在启动时初始化。
<bean id="ConstructorWithParamterDemo"
class="com.ripjava.springdi.ConstructorWithParamterDemo" lazy-init="true" >
</bean>
这样做的好处是更快的初始化时间,但权衡的是,只有在请求bean之后才能发现配置错误,这可能是应用程序运行后几个小时甚至几天。