第3章Spring Bean

学习目的与要求

本章主要介绍Spring Bean的配置、实例化、作用域、生命周期以及装配方式等内容。
通过本章的学习,要求读者了解Spring Bean的生命周期,掌握Spring Bean的配置、实例
化、作用域以及装配方式等内容。

本章主要内容

 .Bean的配置 
.Bean的实例化 
.Bean的作用域 
.Bean的生命周期 
.Bean的装配方式


在Spring的应用中,使用Spring IoC容器可以创建、装配和配置应用组件对象,这里
的组件对象称为Bean。本章重点学习如何将Bean装配注入Spring IoC容器中。

3.1 Bean的配置

Spring可以看作一个大型工厂,用于生产和管理Spring容器中的Bean。如果要使用
这个工厂生产和管理Bean,需要开发者将Bean配置到Spring的配置文件中。Spring框架
支持XML和Properties两种格式的配置文件,在实际开发中常用XML格式的配置文件。

XML配置文件的根元素是<beans>,在<beans>中包含了多个<bean>元素,每个
<bean>元素定义一个Bean,并描述Bean如何被装配到Spring容器中。<bean>元素的常
用属性及其子元素如表3.1所示。

表3.1 <bean>元素的常用属性及其子元素

属性或子元素名称

描 述

id

Bean在BeanFactory中的唯一标识,在代码中通过BeanFactory获取Bean
实例时需要以此作为索引名称

class

Bean的具体实现类,使用类名(例如dao.TestDIDaoImpl)

scope

指定Bean实例的作用域,具体属性值及含义参见3.3节

<constructor-arg>

<bean>元素的子元素,使用构造方法注入,指定构造方法的参数。该元
素的index属性指定参数的序号,ref属性指定对BeanFactory中其他Bean
的引用关系,type属性指定参数的类型,value属性指定参数的常量值







续表

属性或子元素名称

描 述

<property>

<bean>元素的子元素,用于设置一个属性。该元素的name属性指定
Bean实例中的相应属性名称,value属性指定Bean的属性值,ref属性指
定属性对BeanFactory中其他Bean的引用关系

<list>

<property>元素的子元素,用于封装 List或数组类型的依赖注入,具体用
法参见3.5 节

<map>

<property>元素的子元素,用于封装 Map类型的依赖注入,具体用法参
见3.5 节

<set>

<property>元素的子元素,用于封装Set类型的依赖注入,具体用法参
见3.5节

<entry>

<map>元素的子元素,用于设置一个键值对,具体用法参见3.5 节





Bean的配置示例代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://www.springframework.org/schema/beans

 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 使用id属性定义myTestDIDao,其对应的实现类为dao.TestDIDaoImpl -->

 <bean id="myTestDIDao" class="dao.TestDIDaoImpl"/>

 <!-- 使用构造方法注入 -->

 <bean id="testDIService" class="service.TestDIServiceImpl">

 <!-- 给构造方法传引用类型的参数值myTestDIDao -->

 <constructor-arg index="0" ref="myTestDIDao"/>

 </bean>

</beans>

3.2 Bean的实例化

在面向对象编程中,如果想使用某个对象,需要先实例化该对象。同样,在Spring
框架中,如果想使用Spring容器中的Bean,也需要先实例化Bean。Spring框架实例化
Bean有3种方式,即构造方法实例化、静态工厂方法实例化和实例工厂方法实例化(其中,
最常用的实例化方法是构造方法实例化)。

XX3.2.1 构造方法实例化


在Spring框架中,Spring容器可以调用Bean对应类中的无参数构造方法来实例化
Bean,这种方式称为构造方法实例化。下面通过一个实例来演示构造方法实例化Bean的
过程。

【例3-1】 构造方法实例化Bean。

A. 创建模块ch3_1


参考1.3节创建名为ch3的项目,在ch3项目中创建一个名为ch3_1的模块,同时
给ch3_1模块添加Web Application,并将Spring的4个基础包和Spring Commons Logging 
Bridge对应的JAR包spring-jcl-6.0.0.jar复制到ch3_1的WEB-INF/lib目录中,添加为模块

扫一扫


视频讲解



依赖。

B. 创建BeanClass类


在ch3_1模块的src目录下创建instance包,并在该包中创建BeanClass类,代码如下:

package instance;

public class BeanClass { 

 public String message;

 public BeanClass() {

 message = "构造方法实例化Bean";

 }

 public BeanClass(String s) {

 message = s;

 }

}

C. 创建配置文件


在ch3_1模块的src目录下创建Spring的配置文件applicationContext.xml,在配置文
件中定义一个id为constructorInstance的Bean,代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://www.springframework.org/schema/beans

 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 构造方法实例化Bean -->

 <bean id="constructorInstance" class="instance.BeanClass"/>

</beans>

D. 创建测试类


在ch3_1模块的src目录下创建test包,并在该包中创建测试类TestInstance,代码如下:

package test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import instance.BeanClass;

public class TestInstance {

 private static ApplicationContext appCon;

 public static void main(String[] args) {

 appC on = new ClassPathXmlApplicationContext
("applicationContext.xml");

 //测试构造方法实例化Bean

 BeanClass b1 = (BeanClass)appCon.getBean("constructorInstance");

 System.out.println(b1+ b1.message);

 }

}

运行上述测试类,控制台的输出结果如图3.1所示。



图3.1 构造方法实例化Bean的运行结果

XX3.2.2 静态工厂方法实例化


在使用静态工厂方法实例化Bean时,要求开发者在工厂类中创建一个静态方法来创



建Bean的实例。在配置Bean时,需要使用class属性指定静态工厂类,同时还需要使用
factory-method属性指定工厂类中的静态方法。下面通过一个实例来演示静态工厂方法实
例化Bean的过程。

【例3-2】 静态工厂方法实例化Bean。

该实例在例3-1的基础上实现,具体过程如下。

A. 创建工厂类BeanStaticFactory


在instance包中创建工厂类BeanStaticFactory,在该类中有一个静态方法来实例化对
象,具体代码如下:

package instance;

public class BeanStaticFactory {

 priv ate static BeanClass beanInstance = new BeanClass("调用静态工厂方法实
例化Bean");

 public static BeanClass createInstance() {

 return beanInstance;

 }

}

B. 编辑配置文件


在配置文件applicationContext.xml中添加如下配置代码:

<!-- 静态工厂方法实例化Bean,createInstance为静态工厂类BeanStaticFactory中的静态
方法 -->

<bean id="staticFactoryInstance" class="instance.BeanStaticFactory" 
factory-method="createInstance"/>

C. 添加测试代码


在测试类TestInstance中添加如下代码:

//测试静态工厂方法实例化Bean

BeanClass b2 = (BeanClass)appCon.getBean("staticFactoryInstance");

System.out.println(b2 + b2.message);

此时测试类的运行结果如图3.2所示。



图3.2 实例化Bean的运行结果

XX3.2.3 实例工厂方法实例化


在使用实例工厂方法实例化Bean时,要求开发者在工厂类中创建一个实例方法来创
建Bean的实例。在配置Bean时,需要使用factory-bean属性指定配置的实例工厂,同时
还需要使用factory-method属性指定实例工厂中的实例方法。下面通过一个实例来演示实
例工厂方法实例化Bean的过程。

【例3-3】 实例工厂方法实例化Bean。

该实例在例3-2的基础上实现,具体过程如下。

A. 创建工厂类BeanInstanceFactory


在instance包中创建工厂类BeanInstanceFactory,在该类中有一个实例工厂方法来实



例化对象,具体代码如下:

package instance;

public class BeanInstanceFactory {

 public BeanClass createBeanClassInstance() {

 return new BeanClass("调用实例工厂方法实例化Bean");

 }

}

B. 编辑配置文件


在配置文件applicationContext.xml中添加如下配置代码:

<!-- 配置实例工厂 -->

<bean id="myFactory" class="instance.BeanInstanceFactory"/>

<!-- 使用factory-bean属性指定配置工厂,

使用factory-method属性指定使用工厂中的哪个方法实例化Bean -->

<bean id="instanceFactoryInstance" factory-bean="myFactory" 
factorymethod="createBeanClassInstance"/>

C. 添加测试代码


在测试类TestInstance中添加如下代码:

//测试实例工厂方法实例化Bean

BeanClass b3 = (BeanClass)appCon.getBean("instanceFactoryInstance");

System.out.println(b3 + b3.message);

此时测试类的运行结果如图3.3所示。



图3.3 实例化Bean的运行结果

3.3 Bean的作用域

在Spring中不仅可以完成Bean的实例化,还可以为Bean指定作用域。在Spring 6.0
中为Bean的实例定义了如表3.2所示的作用域。

表3.2 Bean的作用域

作用域名称

描 述

singleton

默认的作用域,使用singleton定义的Bean在Spring容器中只有一个Bean实例

prototype

Spring容器每次获取prototype定义的Bean都将创建一个新的Bean实例

request

在一次HTTP请求中容器将返回一个Bean实例,不同的HTTP请求返回不同的
Bean实例。该作用域仅在Web Spring应用程序上下文中使用

session

在一个HTTP Session中容器将返回同一个Bean实例。该作用域仅在Web 
Spring应用程序上下文中使用

application

为每个ServletContext对象创建一个实例,即同一个应用共享一个Bean实例。
该作用域仅在Web Spring应用程序上下文中使用





扫一扫


视频讲解



续表

作用域名称

描 述

websocket

为每个WebSocket对象创建一个Bean实例。该作用域仅在Web Spring应用程
序上下文中使用





在表3.2所示的6种作用域中,singleton和prototype是最常用的两种作用域,后面4
种作用域仅用在Web Spring应用程序上下文中,在本节将会对singleton和prototype作用
域进行详细的讲解。

XX3.3.1 singleton作用域


当将Bean的scope设置为singleton时,Spring IoC容器仅生成和管理一个Bean实例。
在使用id或name获取Bean实例时,IoC容器将返回共享的Bean实例。

由于singleton是scope的默认方式,所以有两种方式将Bean的scope设置为singleton。
配置文件的示例代码如下:

<bean id="constructorInstance" class="instance.BeanClass"/>

或

<bean id="constructorInstance" class="instance.BeanClass" scope=
"singleton"/>

下面通过一个实例来测试singleton作用域。

【例3-4】 测试singleton作用域。

该实例在例3-3的基础上实现,仅需要在测试类中添加如下代码:

BeanClass b4 = (BeanClass)appCon.getBean("instanceFactoryInstance");

System.out.println(b4 + b4.message);

此时测试类的代码具体如下:

package test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import instance.BeanClass;

public class TestInstance {

 private static ApplicationContext appCon;

 public static void main(String[] args) {

 appC on = new ClassPathXmlApplicationContext
("applicationContext.xml");

 //测试构造方法实例化Bean

 BeanClass b1 = (BeanClass)appCon.getBean("constructorInstance");

 System.out.println(b1 + b1.message);

 //测试静态工厂方法实例化Bean

 BeanClass b2 = (BeanClass)appCon.getBean("staticFactoryInstance");

 System.out.println(b2 + b2.message);

 //测试实例工厂方法实例化Bean

 BeanClass b3 = (BeanClass)appCon.getBean("instanceFactoryInstance");

 System.out.println(b3 + b3.message);

 BeanClass b4 = (BeanClass)appCon.getBean("instanceFactoryInstance");

 System.out.println(b4 + b4.message);

 }

}

测试类的运行结果如图3.4所示。





图3.4 singleton作用域的运行结果

从图3.4所示的运行结果可知,在获取多个作用域为singleton的同名Bean实例时,
IoC容器仅返回同一个Bean实例。

XX3.3.2 prototype作用域


当将Bean的scope设置为prototype时,Spring IoC容器将为每次请求创建一个新的
实例。如果将3.2.3节中id为instanceFactoryInstance的Bean定义修改如下:

<bean id="instanceFactoryInstance" factory-bean="myFactory" 
factory-method="createBeanClassInstance" scope="prototype"/>

此时测试类的运行结果如图3.5所示。



图3.5 prototype作用域的运行结果

从图3.5所示的运行结果可知,在获取多个作用域为prototype的同名Bean实例时,
IoC容器将返回多个不同的Bean实例。

3.4 Bean的生命周期

一个对象的生命周期包括创建(实例化与初始化)、使用以及销毁等阶段,在Spring中,
Bean对象的生命周期也遵循这一过程,但是Spring提供了许多对外接口,允许开发者对
3个过程(实例化、初始化、销毁)的前后做一些操作。在Spring中,实例化是为Bean
对象开辟空间,初始化则是对属性的初始化。

Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能
够精确地知道Bean何时被创建,何时初始化完成,以及何时被销毁。对于prototype作用
域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean实例就交给了客户
端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype
作用域的Bean。在Spring中Bean的生命周期的执行是一个很复杂的过程,用户可借鉴
Servlet的生命周期(实例化、初始化init、接收请求service、销毁destroy)理解Bean的
生命周期。

Bean的生命周期的整个过程如下:

扫一扫


视频讲解



(1)根据Bean的配置情况实例化一个Bean。

(2)根据Spring上下文对实例化的Bean进行依赖注入,即对Bean的属性进行初
始化。

(3)如果Bean实现了BeanNameAware接口,将调用它实现的setBeanName (String 
beanId)方法设置Bean的名字,此处参数传递的是Spring配置文件中Bean的ID。

(4)如果Bean实现了BeanFactoryAware接口,将调用它实现的setBeanFactory 
(BeanFactory beanFactory)方法设置Bean工厂,此处参数传递的是当前Spring工厂实例的
引用。

(5)如果Bean实现了ApplicationContextAware接口,将调用它实现的setApplicationContext(ApplicationContext ac)方法设置应用的上下文,此处参数传递的是Spring上下文
实例的引用。

(6)如果Bean关联了BeanPostProcessor接口,将调用预初始化方法postProcessBefore 
Initialization(Object obj, String s)对Bean进行初始化前的操作。

(7)如果Bean实现了InitializingBean接口,将调用afterPropertiesSet()方法对Bean
进行初始化。

(8)如果Bean在Spring配置文件中配置了init-method属性,将自动调用其配置的初
始化方法进行初始化操作。

(9)如果Bean关联了BeanPostProcessor接口,将调用postProcessAfterInitialization (Object 
obj, String s)方法,由于是在Bean初始化结束时调用postProcessAfterInitialization ()方法,也
可用于内存或缓存技术。

以上工作(1~9)完成后就可以使用该Bean,由于该Bean的作用域是singleton,所
以调用的是同一个Bean实例。

(10)当Bean不再需要时将经过销毁阶段,如果Bean实现了DisposableBean接口,
将调用其实现的destroy ()方法将Spring中的Bean销毁。

(11)如果在配置文件中通过destroy-method属性指定了Bean的销毁方法,将调用其
配置的销毁方法进行销毁。

在Spring中,通过实现特定的接口或通过<bean>元素的属性设置可以对Bean的生
命周期过程产生影响。开发者可以随意地配置<bean>元素的属性,但不建议过多地使
用Bean实现接口,因为这样将使代码和Spring的聚合过于紧密。下面通过一个实例演示
Bean的生命周期。

【例3-5】 演示Bean的生命周期。

A. 创建模块ch3_5


在ch3项目中创建一个名为ch3_5的模块,同时给ch3_5模块添加Web Application,
并将Spring的4个基础包和Spring Commons Logging Bridge对应的JAR包spring-jcl-
6.0.0.jar复制到ch3_5的WEB-INF/lib目录中,添加为模块依赖。

B. 创建Bean的实现类


在ch3_5模块的src目录下创建名为life的包,并在life包下创建BeanLife类。在
BeanLife类中有两个方法:一个用于演示初始化过程;另一个用于演示销毁过程。其具体
代码如下:

package life;

public class BeanLife {



 BeanLife(){

 System.out.println("执行构造方法,创建对象。");

 }

 public void initMyself() {

 Syst em.out.println(this.getClass().getName() + "执行自定义的初始化方法");

 }

 public void destroyMyself() {

 System.out.println(this.getClass().getName() +"执行自定义的销毁方法");

 }

}

C. 创建配置文件


在ch3_5模块的src目录下创建Spring的配置文件applicationContext.xml,在配置
文件中定义一个id为beanLife的Bean,使用init-method属性指定初始化方法,使用
destroy-method属性指定销毁方法,具体配置代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 配置Bean,使用init-method属性指定初始化方法,使用destroy-method属性指定
销毁方法 -->

<bean id="beanLife" class="life.BeanLife" init-method="initMyself" 
destroy-method="destroyMyself"/>

</beans>

D. 测试生命周期


在ch3_5模块的src目录下创建一个名为test的包,并在该包中创建测试类TestLife,
具体代码如下:

package test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import life.BeanLife;

public class TestLife {

 public static void main(String[] args) {

 //初始化Spring容器,加载配置文件

 //为了方便演示销毁方法的过行,这里使用ClassPathXmlApplicationContext

 ClassPathXmlApplicationContext ctx = 

 new ClassPathXmlApplicationContext("applicationContext.xml");

 System.out.println("获得对象前");

 BeanLife blife = (BeanLife)ctx.getBean("beanLife");

 System.out.println("获得对象后" + blife);

 ctx.close(); //关闭容器,销毁Bean对象

 }

}

上述测试类运行结果如图3.6所示。



图3.6 Bean的生命周期演示效果



从图3.6可以看出,在加载配置文件时创建Bean对象,执行了Bean的构造方法和初始
化方法initMyself();在获得对象后,关闭容器时,执行了Bean的销毁方法destroyMyself。

3.5 Bean的装配方式

Bean的装配可以理解为将Bean依赖注入Spring容器中,Bean的装配方式即Bean
依赖注入的方式。Spring容器支持基于XML配置的装配、基于注解的装配以及自动装配
等多种装配方式,其中最受人们青睐的装配方式是基于注解的装配(在本书后续章节中
采用基于注解的装配方式装配Bean)。本节将主要讲解基于XML配置的装配和基于注解
的装配。

XX3.5.1 基于XML配置的装配


基于XML配置的装配方式已经有很久的历史了,曾经是主要的装配方式。在通过2.3
节的学习后,大家知道Spring提供了两种基于XML配置的装配方式,即使用构造方法注
入和使用属性的Setter方法注入。

在使用构造方法注入方式装配Bean时,Bean的实现类需要提供带参数的构造方法,
并在配置文件中使用<bean>元素的子元素<constructor-arg>来定义构造方法的参数;在使
用属性的Setter方法注入方式装配Bean时,Bean的实现类需要提供一个默认无参数的构
造方法,并为需要注入的属性提供对应的Setter方法,另外还需要使用<bean>元素的子
元素<property>为每个属性注入值。

下面通过一个实例来讲解基于XML配置的装配方式。

【例3-6】 基于XML配置的装配方式。

A. 创建模块ch3_6


在ch3项目中创建一个名为ch3_6的模块,同时给ch3_6模块添加Web Application,
并将Spring的4个基础包和Spring Commons Logging Bridge对应的JAR包spring-jcl-
6.0.0.jar复制到ch3_6的WEB-INF/lib目录中,添加为模块依赖。

B. 创建Bean的实现类


在ch3_6模块的src目录下创建名为assemble的包,并在该包中创建ComplexUser类,
在ComplexUser类中分别使用构造方法注入和使用属性的Setter方法注入,具体代码如下:

package assemble;

import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.Set;

public class ComplexUser {

 private String uname;

 private List<String> hobbyList;

 private Map<String,String> residenceMap;

 private Set<String> aliasSet;

 private String[] array;

 /*

 * 使用构造方法注入,需要提供带参数的构造方法

 */

 publ ic ComplexUser(String uname, List<String> hobbyList, Map<String, 
String> residenceMap, Set<String> aliasSet, String[] array) {

扫一扫


视频讲解



 super();

 this.uname = uname;

 this.hobbyList = hobbyList;

 this.residenceMap = residenceMap;

 this.aliasSet = aliasSet;

 this.array = array;

 }

 /**

 *使用属性的Setter方法注入,提供默认无参数的构造方法,并为注入的属性提供Setter方法

 */

 public ComplexUser() {

 super();

 }

 /******此处省略所有属性的Setter方法******/

 @Override

 public String toString() {

 retu rn "ComplexUser [uname=" + uname + ", hobbyList=" + hobbyList + 
", residenceMap=" + residenceMap + ", aliasSet=" + aliasSet + 
", array=" + Arrays.toString(array) + "]";

 }

}

C. 创建配置文件


在ch3_6模块的src目录下创建Spring的配置文件applicationContext.xml,在配置文
件中使用实现类ComplexUser配置Bean的两个实例,具体代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:schemaLocation="http://www.springframework.org/schema/beans

 http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 使用构造方法注入方式装配ComplexUser实例user1 -->

 <bean id="user1" class="assemble.ComplexUser">

 <constructor-arg index="0" value="chenheng1"/>

 <constructor-arg index="1">

 <list>

 <value>唱歌</value>

 <value>跳舞</value>

 <value>爬山</value>

 </list>

 </constructor-arg>

 <constructor-arg index="2">

 <map>

 <entry key="dalian" value="大连"/>

 <entry key="beijing" value="北京"/>

 <entry key="shanghai" value="上海"/>

 </map>

 </constructor-arg>

 <constructor-arg index="3">

 <set>

 <value>陈恒100</value>

 <value>陈恒101</value>

 <value>陈恒102</value>

 </set>

 </constructor-arg>

 <constructor-arg index="4">

 <array>

 <value>aaaaa</value>

 <value>bbbbb</value>

 </array>

 </constructor-arg>

 </bean>