7.3.3 构造注入

7.3.3 构造注入

前面已经介绍过,通过setter方法为目标Bean注入依赖关系的方式被称为设值注入;另外还有种注入方式,这种方式在构造实例时,已经为其完成了依赖关系的初始化。这种利**用构造器来设置依赖关系的方式,被称为构造注入**。

构造注入的本质

通俗来说,就是驱动Spring在底层以反射方式执行带参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化—这就是构造注入的本质
现在问题产生了:<bean>元素默认总是驱动Spring调用无参数的构造器来创建对象,那怎样驱动Spring调用有参数的构造器去创建对象呢?答案是使用<constructor-arg>子元素,每个<constructor-arg>子元素代表一个构造器参数,如果<bean>元素包含N<constructor-arg>子元素,就会驱动Spring调用带N个参数的构造器来创建对象.

程序示例

对前面代码中的Chinese类做简单的修改,修改后的代码如下。

Chinese.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;

public class Chinese implements Person
{
private Axe axe;
// 构造注入所需的带参数的构造器
public Chinese(Axe axe)
{
this.axe = axe;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

上面的Chinese类没有提供设置axe成员变量的setter法,仅仅提供了一个带Axe参数的构造器。Spring将通过该构造器为chinese注入所依赖的Bean实例。
构造注入的配置文件也需做简单的修改,为了使用构造注入(也就是驱动Spring调用有参数的构造器创建对象),还需使用<constructor-arg>元素指定构造器的参数。修改后的配置文件如下。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置chinese实例,其实现类是Chinese -->
<!-- 1号代码 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<!-- 下面只有一个constructor-arg子元素, 驱动Spring调用Chinese带一个参数的构造器来创建对象 -->
<constructor-arg ref="steelAxe"
type="org.crazyit.app.service.Axe" />
</bean>
<!-- 配置stoneAxe实例,其实现类是StoneAxe -->
<bean id="stoneAxe"
class="org.crazyit.app.service.impl.StoneAxe" />
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
</beans>

上面配置文件中的粗体字代码使用<constructor-arg>元素指定了一个构造器参数,该参数类型是Axe,这指定Spring调用Chinese类里带一个Axe参数的构造器来创建chinese实例。也就是说,上面代码相当于驱动Spring执行如下代码:

1
2
3
4
5
6
7
String idStr=...;//Spring解析<bean>元素得到id属性值为chinese
String refStr=...;//Spring解析<constructor-arg>元素得到ref属性值为steelAxe
Object paramBean=container.get(refStr);
//Spring会用反射方式执行下面代码,此处为了降低阅读难度,该行代码没有使用反射。
Object obj=new org.crazyit.app.service.impl.Chinese(paramBean);
// container代表Spring容器
container.put(idStr,obj);

从上面字代码可以看出,由于使用了有参数的构造器创建实例,所以当Bean实例被创建完成后,该Bean的依赖关系已经设置完成。
该示例的执行效果与设值注入steelAxe时的执行效果完全一样。区别在于:

  • 创建Person实例中Axe属性的时机不同——设值注入是先通过无参数的构造器创建一个Bean实例,然后调用对应的setter方法注入依赖关系;
  • 而构造注入则直接调用有参数的构造器,当Bean实例创建完成后,已经完成了依赖关系的注入

index属性

配置<constructor-arg>元素时可指定一个index属性,用于指定该构造参数值将作为第几个构造参数值,例如,指定index="0"表明该构造参数值将作为第一个构造参数值
希望Spring调用带几个参数的构造器,就在<bean>元素中配置几个<constructor-arg>子元素。例如如下配置代码:

1
2
3
4
5
<!--定义名为bean1的Bean,对应的实现类为lee.Test1-->
<bean id="bean1" class="lee.Test1">
<constructor-arg index="0" value="hello"/>
<constructor-arg index="1" value="23"/>
</bean>

上面的代码相当于让Spring调用如下代码( Spring底层用反射执行该代码)

1
Object bean1=new lee.Test1("hello","23");//①号代码

由于Spring本身提供了功能强大的类型转换机制,因此如果lee.Test1只包含一个Test(String,int)构造器,那么上面的配置片段相当于让Spring执行如下代码( Spring底层用反射执行该代码):

1
Object bean1=new lee.Test1("hello",23);//②号代码

这就产生一个问题:如果lee.Test1类既有Testl(String, String)构造器,又有Test1(String,int)构造器,那么上面的粗体字配置片段到底让Spring执行哪行代码呢?答案是①号代码,因为此时的配置还不够明确:对于<constructor-arg value="23>,Spring只能解析出一个"23"字符串,但它到底需要转换为哪种数据类型——从配置文件中看不出来,只能根据lee.Test1的构造器来尝试转换.

type属性

为了更明确地指定数据类型, Spring允许为<constructor-arg.>元素指定一个type属性,例如<constructor-arg value="23" type="int">,此处Spring明确知道此处配置了一个int类型的参数。与此类似的是,<value>元素也可指定type属性,用于确定该属性值的数据类型。