原文链接:https://blog.youkuaiyun.com/qq_21396469/article/details/63687202
Spring 支持三种依赖注入的方式,它们分别是属性注入、构造函数注入以及工厂方法注入。首先我们先来认识一下什么是依赖注入。依赖注入就是让调用类对某一接口实现类的依赖关系由第三方注入,以移除调用类对某一接口实现类的依赖。下面将对这三种注入方式分别进行讲述,并且演示相关示例。
1、属性注入
属性注入即通过 setXXX() 方法注入 Bean 的属性值或者依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际中最常用的注入方式。在 Spring 配置文件中 <property> 元素所指定的属性名和 Bean 实现类的 Setter 方法满足 Sun JavaBean 的属性命名规范,即 xxx 的属性对应 setXxx() 的方法。
属性注入要求 Bean 提供一个默认的不带参的构造函数,并且为需要注入的属性提供对应的 set 方法。Spring 先调用 Bean 的默认构造函数实例化 Bean 对象,然后再通过反射的方式调用 set 方法来注入属性值。若类中没有显式地定义构造函数,则 Java 虚拟机会为类添加默认的不带参的构造函数。若类中显式地定义了构造函数,则 Java 虚拟机不会为其添加。
-
public
class Car {
-
private String brand;
-
public void setBrand(String brand) {
-
this.brand = brand;
-
}
-
public String getBrand() {
-
return
this.brand;
-
}
-
}
-
<bean id="car" class="***">
-
<property name="brand">
-
-
</property>
-
</bean>
2、构造注入
构造函数注入是除属性注入之外的另一种常用的注入方式,它保证一些必要的属性在 Bean 实例化时就得到了设置,并在实例化后就可以使用。使用构造函数注入要求 Bean 提供一个默认的带参的构造函数。
(1)按类型匹配入参
-
public
class Car {
-
private String brand;
-
private
double price;
-
public Car(String brand, double price) {
-
this.brand = brand;
-
this.price = price;
-
}
-
}
-
<bean id="car" class="***">
-
<constructor-arg type="String">
-
<value>奔驰
</value>
-
</constructor-arg>
-
<constructor-arg type="double">
-
<value>20000
</value>
-
</constructor-arg>
-
</bean>
(2)按索引匹配入参
上面仅通过 type 属性指定的参数类型就可以确定不同的入参,但是倘若构造函数的两个入参的类型相同,那么仅通过 type 就无法确定对应的关系了。此时需要通过入参索引的方式来进行确定。
-
public
class Car {
-
private String brand;
-
private String corp;
-
private
double price;
-
public Car(String brand, String corp, double price) {
-
this.brand = brand;
-
this.corp = corp;
-
this.price = price;
-
}
-
}
-
<bean id="car" class="***">
-
<constructor-arg index="0" value="奔驰" />
-
<constructor-arg index="1" value="德国奔驰" />
-
<constructor-arg index="2" value="20000" />
-
</bean>
*注意:在属性注入时,Spring 按 JavaBean 的规范确定配置属性和对应的 set 方法,并使用 Java 反射机制调用对应的 set 方法完成属性的注入。但 Java 反射机制并不会记住构造函数的入参名,因此我们无法通过指定构造函数的入参名称来进行构造函数注入的配置,而只能通过入参类型和索引信息间接确定构造函数配置项和入参的对应关系。
(3)联合使用类型和索引匹配入参
在某些复杂的情况下,需要联合使用类型和索引匹配来确定构造函数的配置项与入参的对应关系。
-
public
class Car {
-
private String brand;
-
private String corp;
-
private
double price;
-
private
int maxSpeed;
-
public Car(String brand, String corp, double price) {
-
this.brand = brand;
-
this.corp = corp;
-
this.price = price;
-
}
-
public Car(String brand, String corp, int maxSpeed) {
-
this.brand = brand;
-
this.corp = corp;
-
this.maxSpeed = maxSpeed;
-
}
-
}
-
<bean id="car" class="***">
-
<constructor-arg index="0" type="String">
-
<value>奔驰
</value>
-
</constructor-arg>
-
<constructor-arg index="1" type="String">
-
<value>德国奔驰
</value>
-
</constructor-arg>
-
<constructor-arg index="2" type="int">
-
<value>200
</value>
-
</constructor-arg>
-
</bean>
(4)通过自身类型反射匹配入参
-
public
class Boss {
-
private String name;
-
private Car car;
-
private Office office;
-
public Boss(String name, Car car, Office office) {
-
this.name = name;
-
this.car = car;
-
this.office = office;
-
}
-
}
-
<bean id="boss" class="***">
-
<constructor-arg>
-
<value>John
</value>
-
</constructor-arg>
-
<constructor-arg>
-
<ref bean="car" />
-
</constructor-arg>
-
<constructor-arg>
-
<ref bean="office" />
-
</constructor-arg>
-
</bean>
(5)循环依赖问题
Spring 容器能顺利地实例化以构造函数注入的方式配置的 Bean,有一个前提,即 Bean 构造函数入参引用的对象必须已经准备就绪。由于这个机制的限制,如果两个 Bean 都采用构造函数注入,而且都通过构造函数入参引用了对方,就会发生类似于线程死锁的循环依赖问题。比如下方就是一个循环依赖问题的例子:
-
public
class Car {
-
public Car(String brand, Boss boss) {
-
this.brand = brand;
-
this.boss = boss;
-
}
-
}
-
-
public
class Boss {
-
public Boss(String name, Car car) {
-
this.name = name;
-
this.car = car;
-
}
-
}
-
<bean id="car" class="***">
-
<constructor-arg index="0" value="奔驰" />
-
<constructor-arg index="1" ref="boss" />
-
</bean>
-
-
<bean id="boss" class="***">
-
<constructor-arg index="0" value="John" />
-
<constructor-arg index="1" ref="car" />
-
</bean>
上面的配置文件中,car 这个 Bean 引用了 boss 这个 Bean,而 boss 这个 Bean 又引用了 car 这个 Bean。那么当启动 Spring IoC 容器的时候,因为存在着循环依赖的问题,Spring 容器将无法成功启动。为了解决这个问题,我们需要将构造函数注入的方式调整为使用属性注入的方式。
3、工厂方法注入
工厂方法注入曾是是应用中被经常使用的设计模式,也是控制翻转和单实例设计思想的主要实现方法。由于 Spring 的 IoC 容器以框架的方式提供工厂方法的功能,并以透明的方式开放给开发者,因此很少需要手工编写基于工厂方法的配置文件。
(1)非静态工厂方法:
-
public
class CarFactory {
-
public Car createBenChiCar() {
-
Car car =
new Car();
-
car.setBrand(
"奔驰");
-
retrun car;
-
}
-
}
-
<bean id="carFactory" class="***">
</bean>
-
<bean id="car" factory-bean="carFactory" fatory-method="createBenChiCar">
</bean>
(2)静态工厂方法:比非静态工厂类方法更加方便,因为用户在无需创建工厂类实例的情况下,就可以调用工厂类方法。
-
public
class CarFactory {
-
public static Car createCar() {
-
Car car =
new Car();
-
retrun car;
-
}
-
}
<bean id="car" class="***" fatory-method="createCar"></bean>
1、构造函数注入理由:
(1)构造函数保证重要属性预先设置;
(2)无需提供每个属性的 Setter 方法,减少类的方法个数;
(3)可更好地封装类变量,避免外部错误调用。
2、属性注入理由:
(1)属性过多时,构造函数变得过于臃肿;
(2)构造函数注入的灵活性不强,有时需要为属性注入 null 值;
(3)多个构造函数时,配置上产生歧义,复杂度提高;
(4)构造函数不利于类的继承和扩展;
(5)构造函数注入会引起循环依赖问题。
3、工厂方法注入需要编写额外的类和代码。既然 Spring 容器已经以一种更优雅的方式实现了传统工厂模式的所有功能,我们大可不必再去做这项重复性的工作了。