7.3.2 设值注入

7.3.2 设值注入

设值注入是指IoC容器通过成员变量的setter方法来注入被依赖对象。这种注入方式简单、直观,因而在Spring的依赖注入里大量使用。
下面示例将会对前面示例进行改写,使之更加规范。 Spring推荐面向接口编程。不管是调用者,还是被依赖对象,都应该为之定义接口,程序应该面向它们的接口,而不是面向实现类编程,这样以便程序后期的升级、维护

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\setter
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ ├─SteelAxe.java
│ └─StoneAxe.java
└─Person.java

Person.java

下面先定义一个Person接口,该接口定义了一个Person对象应遵守的规范。下面是Person接口的代码。

1
2
3
4
5
6
7
package org.crazyit.app.service;

public interface Person
{
// 定义一个使用斧子的方法
public void useAxe();
}

Axe.java

下面是Axe接口的代码。

1
2
3
4
5
6
7
package org.crazyit.app.service;

public interface Axe
{
// Axe接口里有个砍的方法
public String chop();
}

Spring推荐面向接口编程,这样可以更好地让规范和实现分离,从而提供更好的解耦。对于一个Java EE应用,不管是DAO组件,还是业务逻辑组件,都应该先定义一个接口,该接口定义了该组件应该实现的功能,但功能的实现则由其实现类提供。

Chinese.java

下面是Person实现类的代码。

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;
// 设值注入所需的setter方法
public void setAxe(Axe axe)
{
this.axe = axe;
}
// 实现Person接口的useAxe方法
public void useAxe()
{
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

上面程序中的粗体字代码实现了Person接口的useAxe()方法,实现该方法时调用了axechop()方法,这就是典型的依赖关系。
回忆一下曾经编写的Java应用,除了最简单的Hello world之外,哪个应用不是A调用B、B调用C、C调用D…这种方式?那Spring的作用呢? Spring容器的最大作用就是以松耦合的方式来管理这种调用关系。在上面的Chinese类中, Chinese类并不知道它要调用的axe实例在哪里,也不知道axe实例是如何实现的,它只是需要调用Axe对象的方法,这个Axe实例将由Spring容器负责注入。

SteelAxe.java

1
2
3
4
5
6
7
8
9
10
11
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;

public class SteelAxe implements Axe
{
public String chop()
{
return "钢斧砍柴真快";
}
}

到现在为止,程序依然不知道Chinese类和哪个Axe实例耦合, Spring当然也不知道! Spring需要使用XML配置文件来指定实例之间的依赖关系.
Spring采用了XML配置文件,从Spring2.0开始, Spring推荐采用XML Schema来定义配置文件的语义约束。当采用XML Schema来定义配置文件的语义约束时,还可利用Spring配置文件的扩展性进一步简化Spring配置。
Spring为基于XML SchemaXML配置文件提供了一些新的标签,这些新标签使配置更简单,使用更方便。关于如何使用Spring所提供的新标签,后面会有更进一步的介绍。
不仅如此,采用基于XML SchemaXML配置文件时, Spring甚至允许程序员开发自定义的配置文件标签,让其他开发人员在Spring配置文件中使用这些标签,但这些通常应由第三方供应商来完成。对普通软件开发人员以及普通系统架构师而言,则通常无须开发自定义的Spring配置文件标签。所以本书也不打算介绍相关方面的内容。
提示:本书并不是一本完整的Spring学习手册,所以本书只会介绍Spring的核心机制,包括loCSpELAOP和资源访问等, SpringHibernate整合, SpringDAO支持和事务管理,以及SpringStruts2整合等内容,这些是Java ee开发所需要的核心知识。而Spring框架的其他方面,本书不会涉及.
下面是本应用所用的配置文件代码。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="GBK"?>
<!-- Spring配置文件的根元素,使用spring-beans.xsd语义约束 -->
<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类 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<!-- 驱动调用chinese的setAxe()方法,将容器中stoneAxe作为传入参数 -->
<property name="axe" ref="stoneAxe" />
</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>

在配置文件中, Spring配置Bean实例通常会指定两个属性。

  1. id:指定该Bean的唯一标识, Spring根据id属性值来管理Bean,程序通过id属性值来访问该Bean实例。
  2. class:指定该Bean的实现类,此处不可再用接口,必须使用实现类, Spring容器会使用XML解析器读取该属性值,并利用反射来创建该实现类的实例。

可以看到Spring管理Bean的灵巧性。BeanBean之间的依赖关系放在配置文件里组织,而不是写在代码里。通过配置文件的指定, Spring能精确地为每个Bean的成员变量注入值。

Spring会自动检测每个<bean>定义里的<property>元素定义, Spring会在调用默认的构造器创建Bean实例之后,立即调用对应的setter法为Bean的成员变量注入值。
下面是主程序的代码,该主程序只是简单地获取了Person实例,并调用该实例的useAxeo方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package lee;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.crazyit.app.service.*;

public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
// 从Spring容器中获取chinese实例
Person p = ctx.getBean("chinese", Person.class);
// 调用useAxe()方法
p.useAxe();
}
}

上面程序中的实现了创建Spring容器,并通过Spring容器来获取Bean实例。从上面程序中可以看出, Spring容器就是一个巨大的工厂,它可以”生产”出所有类型的Bean实例。程序获取Bean实例的方法是getBean()
一旦通过Spring容器获得了Bean实例之后,如何调用Bean实例的方法就没什么特别之处了。执行上面程序,会看到如下执行结果:

1
石斧砍柴好慢

主程序调用PersonuseAxe()方法时,该方法的方法体内需要使用Axe实例,但程序没有任何地方将特定的Person实例和Axe实例耦合在一起。或者说,程序没有为Person实例传入Axe实例,Axe实例由Spring在运行期间注入。
下面是Axe的另一个实现类:SteelAxe

SteelAxe.java

1
2
3
4
5
6
7
8
9
10
11
package org.crazyit.app.service.impl;

import org.crazyit.app.service.*;

public class SteelAxe implements Axe
{
public String chop()
{
return "钢斧砍柴真快";
}
}

将修改后的SteelAxe部署在Spring容器中,只需在Spring配置文件中增加如下一行:

1
2
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe" />

该行重新定义了一个Axe实例,它的id是steelAxe,实现类是SteelAxe。然后修改chineseBean的配置,将原来传入stoneAxe的地方改为传入steelAxe。也就是将

1
<property name="axe" ref="stoneAxe" />

改成:

1
<property name="axe" ref="steelAxe" />

此时再次执行程序,将得到如下结果:
从上面这种切换可以看出,因为chinese实例与具体的Axe实现类没有任何关系, chinese实例仅仅与Axe接口耦合,这就保证了chinese实例与Axe实例之间的松耦合—这也是Spring强调面向接口编程的原因。

设值注入

BeanBean之间的依赖关系由Spring管理,** Spring采用setter方法为目标Bean注入所依赖的Bean这种方式被称为设值注入**。
从上面示例程序中应该可以看出,依赖注入以配置文件管理Bean实例之间的耦合,让Bean实例之间的耦合从代码层次分离出来。依赖注入是一种优秀的解耦方式。

Spring IoC容器的三个基本要点

经过上面的介绍,不难发现使用Spring loC容器的三个基本要点:

  1. 应用程序的各组件面向接口编程。面向接口编程可以将组件之间的耦合关系提升到接口层次从而有利于项目后期的扩展。
  2. 应用程序的各组件不再由程序主动创建,而是由Spring容器来负责产生并初始化
  3. Spring采用配置文件注解来管理Bean的实现类、依赖关系, Spring容器则根据配置文件或注解,利用反射来创建实例,并为之注入依赖关系