第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>