7.9 容器中Bean的生命周期

Spring可以管理singleton作用域的Bean的生命周期

Spring可以管理singleton作用域的Bean的生命周期, Spring可以精确地知道该Bean何时被创建何时被初始化完成、容器何时准备销毁Bean实例。

Spring无法管理prototype作用域的Bean

对于prototype作用域的Bean, Spring容器仅仅负责创建,当容器创建了Bean实例之后,Bean实例完全交给客户端代码管理,容器不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring都会产生一个新的实例, Spring容器无从知道它曾经创建了多少个prototype作用域的Bean,也无从知道这些prototype作用域的Bean什么时候才会被销毁。因此, **Spring无法管理prototype作用域的Bean**。
对于singleton作用域的Bean,每次客户端代码请求时都返回同一个共享实例,客户端代码不能控制Bean的销毁, Spring容器负责跟踪Bean实例的产生、销毁。 Spring容器可以在创建Bean之后,进行某些通用资源申请:还可以在销毁Bean实例之前,先回收某些资源,比如数据库连接。

管理Bean生命周期行为的时机

对于singleton作用域的Bean, Spring容器知道Bean何时实例化结束、何时销毁, Spring可以管理实例化结束之后销毁之前的行为。管理Bean的生命周期行为主要有如下两个时机。

  1. 注入依赖关系之后。
  2. 即将销毁Bean之前

7.8.5 强制初始化Bean

在大多数情况下,Bean之间的依赖非常直接, Spring容器在返回Bean实例之前,先要完成Bean依赖关系的注入。假如Bean A依赖于Bean B,程序请求Bean A时, Spring容器会自动先初始化Bean B,再将Bean B注入Bean a,最后将具备完整依赖的Bean a返回给程序。
在极端的情况下,Bean之间的依赖不够直接。比如,某个类的初始化块中使用其他Bean, Spring总是先初始化主调Bean,当执行初始化块时,被依赖Bean可能还没实例化,此时将引发异常。

depends-on属性

为了显式指定被依赖Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。配置片段如下:

1
2
3
<!-- 配置 beanOne,使用 depends-on强制在初始化beanOne之前先初始化manager这个Bean -->
<bean id="beanOne" class="lee.ExampleBean" depends-on="manager"/>
<bean id="manager" class="org.crazyit.app.service"/>

7.8.4 获得Bean本身的id

对于实际的Java应用而言,BeanBean之间的关系是通过依赖注入管理的,通常不会通过调用容器的getBean()方法来获取Bean实例。可能的情况是,应用中已经获得了Bean实例的引用,但程序无法知道配置该Bean时指定的id,可是程序又确实需要获取配置该Bean时指定的id属性
此外,当程序员在开发一个Bean类时,该Bean何时被部署到Spring容器中,部署到Spring容器时所指定的id是什么,开发该Bean类的程序员无法提前预知.
在某些极端情况下,业务要求程序员在开发Bean类时能预先知道该Bean的配置id,此时可借Spring提供的BeanNameAware接口,通过BeanNameAware接口即可提前预知该Bean的配置id
BeanNameAware接口提供了一个方法:

方法 描述
setName(String name) 该方法的name参数就是Beanid,实现该方法的Bean类就可通过该方法来获得部署该Beanid

BeanNameAware接口中的setBeanName(String name)方法与前面介绍的BeanFactoryAwareApplicationContextAware两个接口中的setter方法一样,这个setter方法不是由程序员来调用的,该方法由Spring容器负责调用—当Spring容器调用这个setter方法时,会把部署该Beanid属性作为参数传入。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\BeanNameAware
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
└─Chinese.java

Chinese.java

下面定义了一个Bean,该Bean实现了BeanNameAware接口。

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

import org.springframework.beans.factory.BeanNameAware;

public class Chinese implements BeanNameAware
{
// 保存部署该Bean时指定的id属性
private String beanName;
// Spring容器会在创建该Bean之后,自动调用它的setBeanName()方法,
// 调用该方法时,会将该Bean的配置id作为参数传给该方法
public void setBeanName(String name)
{
this.beanName = name;
}
public void info()
{
System.out.println("Chinese实现类" + ", 部署该Bean时指定的id为" + beanName);
}
}

上面的Chinese类实现了BeanNameAware接口,并实现了该接口提供的setBeanName()方法。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?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">
<!-- Spring容器会检测容器中所有Bean,如果发现某个Bean实现了BeanNameAware接口,
Spring容器会在创建该Bean之后,自动调用该Bean的setBeanName()方法,
调用该方法时,会将该Bean的配置id作为参数传给该方法
该方法的实现部分将Spring传入的参数(Bean的配置id)赋为给该Chinese对象的
beanName实例变量,因此接下来即可通过该beanName实例变量来访问Bean的配置id。-->
<bean id="chinese" class="org.crazyit.app.service.Chinese"/>
</beans>

Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了BeanNameAware接口, Spring容器就会在创建该Bean之后,自动调用该BeansetBeanName()方法,调用该方法时,会将该Bean的配置id作为参数传给该方法—该方法的实现部分将Spring传入的参数(Bean的配置id)赋给该Chinese对象的beanName实例变量,因此接下来即可通过该beanName实例变量来访问Bean的配置id.

SpringTest.java

将该Bean部署在容器中,该Bean的部署与普通Bean的部署没有任何区别。在主程序中通过如下代码测试

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

import org.springframework.context.*;
import org.springframework.context.support.*;

import org.crazyit.app.service.*;

public class SpringTest
{
public static void main(String[] args)
{
// 创建Spring容器,容器会自动预初始化所有singleton Bean实例
@SuppressWarnings("resource")
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
Chinese chin = ctx.getBean("chinese" , Chinese.class);
chin.info();
}
}

执行结果

1
Chinese实现类, 部署该Bean时指定的id为chinese

从代码执行结果可以看到, Spring容器初始化Chinese这个Bean时回调setBeanName()方法,回调该方法时,该Bean的配置id将会作为参数传给beanName实例变量,这样该Bean的其他方法即可通过beanName实例变量来访问该Bean的配置id

小结

Bean类中需要获得Bean的配置id的情形并不是特别常见的,但如果有这种需即可考虑让Bean类实现BeanNameAware接口。

7.8.3 容器中的工厂Bean

此处的工厂Bean,与前面介绍的实例工厂方法创建Bean,或者静态工厂方法创建Bean的工厂有所区别:前面那些工厂是标准的工厂模式, Spring只是负责调用工厂方法来创建Bean实例;此处的工厂BeanSpring的一种特殊Bean,这种工厂Bean必须实现FactoryBean接口。
FactoryBean接口是工厂Bean的标准接口,把工厂Bean(实现FactoryBean接口的Bean)部署在容器中之后,如果程序通过getBean()方法来获取它时,容器返回的不是FactoryBean实现类的实例,而是返回FactoryBean的产品(即该工厂BeangetObject()方法的返回值)
FactoryBean接口提供如下三个方法。

FactoryBean接口方法 描述
T getObject() 该方法负责返回该工厂Bean生成的Java实例。
Class<?> getObjectType() 该方法返回该工厂Bean生成的Java实例的类型。
boolean isSingleton() 该方法用于判断该工厂Bean生成的Java实例是否为单例模式。

配置FactoryBean与配置普通Bean的定义没有区别,但当程序向Spring容器请求获取该Bean时,容器返回该FactoryBean产品,而不是返回该FactoryBean本身。
从上面介绍不难发现,实现FactoryBean接口的最大作用在于:Spring容器并不是简单地返回该Bean的实例,而是返回该Bean实例的getObject()方法的返回值,而getObject()方法则由开发者负责实现,这样开发者希望Spring返回什么,只要按需求重写getObject()方法即可。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\GetFieldFactoryBean
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─factory\
└─GetFieldFactoryBean.java

GetFieldFactoryBean.java

下面定义了一个标准的工厂Bean,这个工厂Bean实现了FactoryBean接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.crazyit.app.factory;

import java.lang.reflect.*;
import org.springframework.beans.factory.FactoryBean;

public class GetFieldFactoryBean implements FactoryBean<Object>
{
private String targetClass;
private String targetField;
// targetClass的setter方法
public void setTargetClass(String targetClass)
{
this.targetClass = targetClass;
}

// targetField的setter方法
public void setTargetField(String targetField)
{
this.targetField = targetField;
}
// ############################ 实现FactoryBean接口中的方法
// 返回工厂Bean所生产的产品
public Object getObject() throws Exception
{
Class<?> clazz = Class.forName(targetClass);
Field field = clazz.getField(targetField);
return field.get(null);
}
// 获取工厂Bean所生产的产品的类型
public Class<? extends Object> getObjectType()
{
return Object.class;
}
// 返回该工厂Bean所生成的产品是否为单例
public boolean isSingleton()
{
return false;
}
}

上面的GetFieldFactoryBean是一个标准的工厂Bean,该工厂Bean的关键代码就在所实现的getObect()方法,该方法的执行体先使用反射获取targetClass对应的Class对象,再获取targetField对应的类变量的值。 GetFieldFactoryBeantargetClasstargetField都提供了setter方法,因此可接受Spring的设值注入,这样即可让GetFieldFactoryBean获取指定类的、指定静态Field的值。
由于程序不需要让GetFieldFactoryBeangetObject()方法产生的值是单例的,故该工厂类的isSingleton()方法返回false.
下面配置文件将使用GetFieldFactoryBean来获取指定类的、指定静态Field的值。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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">
<!-- 下面配置相当于如下代码:
FactoryBean factory = new org.crazyit.app.factory.GetFieldFactoryBean();
factory.setTargetClass("java.awt.BorderLayout");
factory.setTargetField("NORTH");
north = factory.getObject(); -->
<bean id="north" class="org.crazyit.app.factory.GetFieldFactoryBean">
<property name="targetClass" value="java.awt.BorderLayout"/>
<property name="targetField" value="NORTH"/>
</bean>
<!-- 下面配置相当于如下代码:
FactoryBean factory = new org.crazyit.app.factory.GetFieldFactoryBean();
factory.setTargetClass("java.sql.ResultSet");
factory.setTargetField("TYPE_SCROLL_SENSITIVE");
theValue = factory.getObject(); -->
<bean id="theValue" class="org.crazyit.app.factory.GetFieldFactoryBean">
<property name="targetClass" value="java.sql.ResultSet"/>
<property name="targetField" value="TYPE_SCROLL_SENSITIVE"/>
</bean>
</beans>

从上面的程序可以看出,部署工厂Bean与部署普通Bean其实没有任何区别,同样只需为该Bean配置idclass两个属性即可,但SpringFactoryBean接口的实现类的处理有所不同。

Spring容器对工厂Bean的处理过程

Spring容器会自动检测容器中的所有Bean,如果发现某个Bean实现类实现了FactoryBean接口,Spring容器就会在实例化该Bean、根据<property>执行setter方法之后,额外调用该BeangetObject()方法,并将该方法的返回值作为容器中的Bean.
下面程序示范了获取容器中的FactoryBean的产品。

SpringTest.java

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

import org.springframework.context.*;
import org.springframework.context.support.*;

public class SpringTest
{
public static void main(String[] args)throws Exception
{
@SuppressWarnings("resource")
ApplicationContext ctx = new
ClassPathXmlApplicationContext("beans.xml");
// 下面2行代码获取的FactoryBean的产品
System.out.println(ctx.getBean("north"));
System.out.println(ctx.getBean("theValue"));
// 下面代码可获取的FactoryBean本身
System.out.println(ctx.getBean("&theValue"));
}
}

上面程序main方法中前两行输出代码,直接请求容器中的FactoryBean, Spring将不会返回该FactoryBean本身,而是返回该FactoryBean的产品;
程序中的第三行输出代码代码在Bean id前增加&符号,这将会让Spring返回FactoryBean本身。

测试

编译、运行该程序,可以看到如下输出:

1
2
3
North
1005
org.crazyit.app.factory.GetFieldFactoryBean@78c03f1f

从上面三行输出可以看出,使用该GetFieldFactoryBean即可让程序自由获取任意类的、任意静态Field的值。实际上, Spring框架本身提供了一个FieldRetrievingFactoryBean,这个FactoryBean与我们自己实现的GetFieldFactoryBean具有基本相同的功能,7.10节会详细介绍FieldRetrievingFactoryBean的功能和用法。

实际上, FactoryBeanSpring中非常有用的一个接口, Spring内置提供了很多实用的工厂Bean,例如TransactionProxyFactoryBean等,这个工厂Bean专门用于为目标Bean创建事务代理。
Spring提供的工厂Bean,大多以Factory Bean后缀结尾,并且大多用于生产一批具有某种特征的Bean实例,工厂BeanSpring的一个重要工具类.

7.8.2 Bean继承与Java继承的区别

Spring中的Bean继承和Java中的继承的不同之处

Spring中的Bean继承与Java中的继承截然不同。

  • Spring中的Bean继承是实例与实例之间参数值的延续,Java中的继承则是一般到特殊的细化;
  • Spring中的Bean继承是对象与对象之间的关系,Java中的继承则是类与类之间的关系。

Spring中的Bean继承和java中的类继承的区别

Spring中**Bean的继承Java类的继承**有如下区别。

  • Spring中的子Bean和父Bean可以是不同类型,但Java中的类继承则可保证子类是一种特殊的父类。
  • SpringBean的继承是实例之间的关系,因此主要表现为参数值的延续;而Java中的类继承是类之间的关系,主要表现为方法、属性的延续。
  • Spring中的子Bean不可作为父Bean使用,不具备多态性;Java中的子类实例完全可当成父类实例使用。

7.8 深入理解容器中的Bean

Spring框架绝大部分工作都集中在对容器中Bean的管理上,包括管理容器中Bean的生命周期、使用Bean继承等特殊功能。通过深入的管理,应用程序可以更好地使用这些Java组件(容器中的Bean对应用而言,往往是一个组件)。

7.8.1 抽象Bean与子Bean

在实际开发中,可能出现的场景是:随着项目越来越大, Spring配置文件中出现了多个<bean>配置具有大致相同的配置信息,只有少量信息不同,这将导致配置文件出现很多重复的内容。如果保留这种配置,则可能导致的问题是:

  • 配置文件臃肿。
  • 后期难以修改、维护。

为了解决上面问题,可以考虑把多个<bean>配置中相同的信息提取出来,集中成配置模板——这个配置模板并不是真正的Bean,因此Spring不应该创建该配置模板,于是需要**为该<bean>配置增加abstract="true"属性,这就是抽象Bean**。
抽象Bean不能被实例化, Spring容器不会创建抽象Bean实例。抽象Bean的价值在于被继承,抽象Bean通常作为父Bean让子Bean继承。
抽象Bean只是配置信息的模板,指定abstract="true"属性即可阻止Spring实例化该Bean,因此抽象Bean可以不指定class属性。

抽象Bean不能实例化

抽象Bean不能实例化,因此既不能通过getBean()显式地获得抽象Bean实例,也不能将抽象Bean注入成其他Bean依赖。不管怎样,只要程序企图实例化抽象Bean,都将导致错误.

父Bean中可以继承的配置信息

将大部分相同信息配置成抽象Bean之后,将实际的Bean实例配置成该抽象Bean的子Bean即可。子Bean定义可以从父Bean继承实现类构造器参数属性值配置信息,除此之外,子Bean配置可以增加新的配置信息,并可指定新的配置信息覆盖父Bean的定义。

子bean元素通过parent属性指定其父bean

通过为一个<bean>元素指定parent属性即可指定该Bean的父Bean, parent属性指定该Bean所继承的父Beanid.

父Bean中无法被继承的属性

Bean无法从父Bean继承如下属性:depends-onautowiresingletonscopelazy-init,这些属性将总是从子Bean定义中获得,或采用默认值
修改上面的配置文件如下,增加了子Bean定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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">
<!-- 定义Axe实例 -->
<bean id="steelAxe"
class="org.crazyit.app.service.impl.SteelAxe" />
<!-- 指定abstract="true"定义抽象Bean -->
<bean id="personTemplate" abstract="true">
<property name="name" value="crazyit" />
<property name="axe" ref="steelAxe" />
</bean>
<!-- 通过指定parent属性指定下面Bean配置可从父Bean继承得到配置信息 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese"
parent="personTemplate" />
<bean id="american"
class="org.crazyit.app.service.impl.American"
parent="personTemplate" />
</beans>

在配置文件中chineseamerican Bean都指定了parent="personTemplate"属性,这表明这两个Bean都可从父Bean那里继承得到配置信息,虽然这两个Bean都没有直接指定<property>子元素,但它们会从personTemplate模板那里继承得到两个<property>子元素。也就是说,上面的配置信息实际上相当于如下配置:

1
2
3
4
5
6
7
8
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<property name="name" value="crazyit" />
<property name="axe" ref="steelAxe" />
</bean>
<bean id="american" class="org.crazyit.app.service.impl.American">
<property name="name" value="crazyit" />
<property name="axe" ref="steelAxe" />
</bean>

使用抽象Bean的好处

不使用抽象Bean的配置方式不仅会导致配置文件臃肿,而且不利于项目后期的修改、维护,如果有一天项目需要改变chineseamericanname或所依赖的Axe对象,程序需要逐个修改每个Bean的配置信息。
如果使用了抽象Bean,则只需要修改Bean模板的配置即可,所有继承该Bean模板的子Bean的配置信息都会随之改变。

子类会覆盖父类继承来的相同的配置信息

如果父Bean(抽象Bean)指定了class属性,那么子Beanclass属性都可省略,子Bean将用与父Bean相同的实现类。除此之外,子Bean也可覆盖父Bean的配置信息:当子Bean拥有和父Bean相同的配置信息时,将使用以子Bean的配置信息。

项目结构

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

7.7.3 调用实例工厂方法创建Bean

实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需使用工厂类即可,而调用实例工厂方法则需要工厂实例。所以配置实例工厂方法与配置静态工厂方法基本相似,只有一点区别:

  • 配置静态工厂方法使用class指定静态工厂类,
  • 而配置实例工厂方法则使用factory-bean指定工厂实例

使用实例工厂方法时,配置Bean实例的<bean>.元素无须class属性,因为Spring容器不再直接实例化该Bean, Spring容器仅仅调用实例工厂的工厂方法,工厂方法负责创建Bean实例。
采用实例工厂方法创建Bean<bean>元素时需要指定如下两个属性。

属性 描述
factory-bean 该属性的值为工厂Beanid
factory-method 该属性指定实例工厂的工厂方法。

与静态工厂方法相似,如果需要在调用实例工厂方法时传入参数,则使用<constructor-arg>元素指定参数值。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\instanceFactory
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
├─factory\
│ └─PersonFactory.java
└─service\
├─impl\
│ ├─American.java
│ └─Chinese.java
└─Person.java

Person.java

下面先定义一个Person接口,实例工厂方法所产生的对象将实现Person接口

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

public interface Person
{
// 定义一个打招呼的方法
public String sayHello(String name);
// 定义一个告别的方法
public String sayGoodBye(String name);
}

该接口定义了Person的规范,该接口必须拥有两个方法:能打招呼、能告别,实现该接口的类必须实现这两个方法。下面是Person接口的第一个实现类:American

American.java

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

import org.crazyit.app.service.*;

public class American implements Person
{
// 实现Person接口必须实现如下两个方法
public String sayHello(String name)
{
return name + ",Hello!";
}
public String sayGoodBye(String name)
{
return name + ",Good Bye!";
}
}

Chinese.java

下面是Person接口的第二个实现类:Chinese:

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

import org.crazyit.app.service.*;

public class Chinese implements Person
{
// 实现Person接口必须实现如下两个方法
public String sayHello(String name)
{
return name + ",您好";
}
public String sayGoodBye(String name)
{
return name + ",下次再见";
}
}

PersonFactory是负责产生Person对象的实例工厂,该工厂类里提供了一个getPerson()方法,该方法根据传入的ethnic参数决定产生哪种Person对象。工厂类的代码如下。

PersonFactory.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.factory;

import org.crazyit.app.service.*;
import org.crazyit.app.service.impl.*;

public class PersonFactory
{
// 获得Person实例的实例工厂方法
// ethnic参数决定返回哪个Person实现类的实例
public Person getPerson(String ethnic)
{
if (ethnic.equalsIgnoreCase("chin"))
{
return new Chinese();
} else
{
return new American();
}
}
}

上面的PersonFactory就是一个简单的Person工厂, getPerson()方法就是负责生产Person的工厂方法。由于getPerson()方法没有使用static修饰,因此这只是一个实例工厂方法。
配置实例工厂创建Bean与配置静态工厂创建Bean基本相似,只需将原来的静态工厂类改为现在的工厂实例即可。该应用的配置文件如下。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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">
<!-- 配置工厂Bean,该Bean负责产生其他Bean实例 -->
<bean id="personFactory" class="org.crazyit.app.factory.PersonFactory"/>
<!-- 下面配置驱动Spring调用personFactory这个Bean的getPerson()方法来创建Bean
该bean元素包含的constructor-arg元素用于为工厂方法指定参数,
因此这段配置会驱动Spring以反射方式来执行如下代码:
PersonFactory pf = container.get("personFactory"); // container代表Spring容器
chinese = pf.getPerson("chin"); -->
<bean id="chinese" factory-bean="personFactory"
factory-method="getPerson">
<!-- 配置实例工厂方法的参数 -->
<constructor-arg value="chin"/>
</bean>
<!-- 下面配置会驱动Spring以反射方式来执行如下代码:
PersonFactory pf = container.get("personFactory"); // container代表Spring容器
american = pf.getPerson("ame"); -->
<bean id="american" factory-bean="personFactory"
factory-method="getPerson">
<constructor-arg value="ame"/>
</bean>
</beans>

调用实例工厂方法创建Bean和调用静态工厂方法创建Bean的异同

调用实例工厂方法创建Bean,与调用静态工厂方法创建Bean的用法基本相似。区别如下。

  • 配置实例工厂方法创建Bean,必须将实例工厂配置成Bean实例;而配置静态工厂方法创建Bean,则无须配置工厂Bean
  • 配置实例工厂方法创建Bean,必须使用factory-bean属性确定工厂Bean;而配置静态工厂方法创建Bean,则使用class元素确定静态工厂类

相同之处如下:

  • 都需要使用factory-method属性指定产生Bean实例的工厂方法。
  • 工厂方法如果需要参数,都使用<constructor-arg>元素指定参数值。
  • 普通的设值注入,都使用<property>元素确定参数值。

7.7.2 使用静态工厂方法创建Bean

使用静态工厂方法创建Bean实例时, class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类, Spring通过该属性知道由哪个工厂类来创建Bean实例。
除此之外,还需要使用factory-method属性来指定静态工厂方法, Spring将调用静态工厂方法(可能包含一组参数)返回一个Bean实例,一旦获得了指定Bean实例, Spring后面的处理步骤与采用普通方法创建Bean实例则完全一样。
下面的Bean要由factory-method指定的静态工厂方法来创建,所以这个<bean>元素的class属性指定的是静态工厂类, factory-method指定的工厂方法必须是静态的。
由此可见,采用静态工厂方法创建Bean实例时,<bean>元素需要指定如下两个属性:

属性 描述
class 该属性的值设置为静态工厂类的类名.
factory- method 该属性指定静态工厂方法来生产Bean实例。

如果静态工厂方法需要参数,则使用<constructor-arg>元素传入。

程序示例

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\staticFactory
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
├─factory\
│ └─BeingFactory.java
└─service\
├─Being.java
└─impl\
├─Cat.java
└─Dog.java

下面先定义一个Being接口,静态工厂方法所生产的产品是该接口的实例。下面是接口的两个实现类,静态工厂方法将会产生这两个实现类的实例。

Cat.java

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

public class Cat implements Being
{
private String msg;
// msg的setter方法
public void setMsg(String msg)
{
this.msg = msg;
}
// 实现接口必须实现的testBeing方法
public void testBeing()
{
System.out.println(msg + ",猫喜欢吃老鼠");
}
}

Dog.java

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

import org.crazyit.app.service.*;

public class Dog implements Being
{
private String msg;
// msg的setter方法
public void setMsg(String msg)
{
this.msg = msg;
}
// 实现接口必须实现的testBeing()方法
public void testBeing()
{
System.out.println(msg + ",狗爱啃骨头");
}
}

BeingFactory.java

下面的BeingFactory工厂包含了一个getBeing()静态方法,该静态方法用于返回一个Being实例这就是典型的静态工厂类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.crazyit.app.factory;

import org.crazyit.app.service.Being;
import org.crazyit.app.service.impl.Cat;
import org.crazyit.app.service.impl.Dog;

public class BeingFactory
{
// 返回Being实例的静态工厂方法
// arg参数决定返回哪个Being类的实例
public static Being getBeing(String arg)
{
// 调用此静态方法的参数为dog,则返回Dog实例
if (arg.equalsIgnoreCase("dog"))
{
return new Dog();
}
// 否则返回Cat实例
else
{
return new Cat();
}
}
}

上面的BeingFactory类是一个静态工厂类,该类的getBeing()方法是一个静态工厂方法,该方法根据传入的参数决定返回Cat对象,还是Dog对象。
如果需要指定SpringBeingFactory来生产Being对象,则应该按如下静态工厂方法的方式来配置DogBean,CatBean。本应用中的Spring配置文件如下。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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">
<!-- 下面配置驱动Spring调用BeingFactory的静态getBeing()方法来创建Bean
该bean元素包含的constructor-arg元素用于为静态工厂方法指定参数,
因此这段配置会驱动Spring以反射方式来执行如下代码:
dog = org.crazyit.app.factory.BeingFactory.getBeing("dog");
-->
<bean id="dog" class="org.crazyit.app.factory.BeingFactory"
factory-method="getBeing">
<!-- 配置静态工厂方法的参数 -->
<constructor-arg value="dog" />
<!-- 驱动Spring以"我是狗"为参数来执行dog的setMsg()方法 -->
<property name="msg" value="我是狗" />
</bean>
<!-- 下面配置会驱动Spring以反射方式来执行如下代码:
cat = org.crazyit.app.factory.BeingFactory.getBeing("cat"); -->
<bean id="cat" class="org.crazyit.app.factory.BeingFactory"
factory-method="getBeing">
<!-- 配置静态工厂方法的参数 -->
<constructor-arg value="cat" />
<!-- 驱动Spring以"我是猫"为参数来执行dog的setMsg()方法 -->
<property name="msg" value="我是猫" />
</bean>
</beans>

从上面的配置文件可以看出,catdog两个Bean配置的cass属性和factory-method属性完全相同——这是因为这两个实例都是由同一个静态工厂类、同一个静态工厂方法生产得到的。配置这两个Bean实例时指定的静态工厂方法的参数值不同,配置工厂方法的参数值使用<contructor-arg>元素,如上配置文件所示。
一旦为<bean>元素指定了factory-method属性, Spring就不再调用构造器来创建Bean实例,而是调用工厂方法来创建Bean实例。如果同时指定了classfactory-method两个属性, Spring就会调用静态工厂方法来创建Bean。上面两段配置驱动Spring执行的Java代码已在注释中给出。
主程序获取Spring容器的catdog两个Bean实例的方法依然无须改变,只需要调用Spring容器的getBean()方法即可。主程序如下。

SpringTest.java

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

import org.springframework.context.*;
import org.springframework.context.support.*;

import org.crazyit.app.service.*;

public class SpringTest
{
public static void main(String[] args)
{
// 以类加载路径下的beans.xml配置文件创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new
ClassPathXmlApplicationContext("beans.xml");
Being b1 = ctx.getBean("dog" , Being.class);
b1.testBeing();
Being b2 = ctx.getBean("cat" , Being.class);
b2.testBeing();
}
}

使用静态工厂方法创建实例时必须提供工厂类,工厂类包含产生实例的静态工厂方法。

使用静态工厂方法创建实例时对配置文件的要求

通过静态厂方法创建实例时需要对配置文件进行如下改变:

  1. class属性的值不再是Bean实例的实现类,而是生成Bean实例的静态工厂类
  2. 使用factory-method属性指定创建Bean实例的静态工厂方法
  3. 如果静态工厂方法需要参数,则使用<constructor-arg>元素指定静态工厂方法的参数。

指定Spring使用静态工厂方法来创建Bean实例时, Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,将该静态工厂方法的返回值作为Bean实例。在这个过程中, Spring不再负责创建Bean实例,Bean实例是由用户提供的静态工厂类负责创建的.

当静态工厂方法创建了Bean实例后, Spring依然可以管理该Bean实例的依赖关系,包括为其注入所需的依赖Bean、管理其生命周期等。

7.7 创建Bean的3种方式

在大多数情况下, Spring容器直接通过new关键字调用构造器来创建Bean实例,而clas属性指定了Bean实例的实现类。因此,<bean.元素必须指定Bean实例的class属性,但这并不是实例化Bean的唯一方法.
Spring支持使用如下方式来创建Bean:

  1. 调用构造器创建Bean
  2. 调用静态工厂方法创建Bean
  3. 调用实例工厂方法创建Bean

7.7.1 使用构造器创建Bean实例

使用构造器来创建Bean实例是最常见的情况,如果不采用构造注入, Spring底层会调用Bean类的无参数构造器来创建实例,因此要求该Bean类提供无参数的构造器。在这种情况下, class元素是必需的(除非采用继承), class属性的值就是Bean实例的实现类。

采用设值注入时

如果采用设值注入, Spring容器将使用默认的构造器来创建Bean实例, SpringBean实例的所有属性执行默认初始化,即:

  • 所有基本类型的值初始化为0false;
  • 所有引用类型的值初始化为null;

接下来, BeanFactory会根据配置文件决定依赖关系,先实例化被依赖的Bean实例,然后为Bean注入依赖关系,最后将一个完整的Bean实例返回给程序。

采用构造注入时

如果采用构造注入,则要求配置文件为<bean>元素添加<constructor-arg>子元素,每个<constructor-arg>子元素配置一个构造器参数。 Spring容器将使用带对应参数的构造器来创建Bean实例, Spring调用构造器传入的参数即可用于初始化Bean的实例变量,最后也将一个完整的Bean实例返回给程序。

7.6 Spring提供的Java配置管理

Spring为不喜欢XML的人提供了一种选择:如果不喜欢使用XML来管理Bean,以及Bean之间的依赖关系, Spring允许开发者使用Java类进行配置管理
假如有如下Person接口的实现类。

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

import org.crazyit.app.service.Axe;
import org.crazyit.app.service.Person;

public class Chinese implements Person
{
private Axe axe;
private String name;
// axe的setter方法
public void setAxe(Axe axe)
{
this.axe = axe;
}
// name的setter方法
public void setName(String name)
{
this.name = name;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法,表明Person对象依赖于axe对象
System.out.println("我是:" + name
+ axe.chop());
}
}

上面的Chinese类需要注入两个属性:nameaxe,本示例当然也为Axe提供了两个实现类StoneAxeSteelAxe。如果采用XML配置,相应的配置文件如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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 -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<!-- 驱动Spring执行setAxe()方法,以容器中id为stoneAxe的Bean为参数 -->
<property name="axe" ref="stoneAxe"/>
<!-- 驱动Spring执行setName()方法,以字符串"孙悟空"为参数 -->
<property name="name" value="孙悟空"/>
</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>

如果开发者不喜欢使用XML配置文件, Spring允许开发者使用Java类进行配置上面的XML配置文件可以替换为如下的Java配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package org.crazyit.app.config;

import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.*;

import org.crazyit.app.service.*;
import org.crazyit.app.service.impl.*;

@Configuration
public class AppConfig
{
// 相当于定义一个名为personName的变量,其值为"孙悟空"
@Value("孙悟空") String personName;
// 配置一个Bean:chinese
@Bean(name="chinese")
public Person person()
{
Chinese p = new Chinese();
p.setAxe(stoneAxe());
p.setName(personName);
return p;
}
// 配置Bean:stoneAxe
@Bean(name="stoneAxe")
public Axe stoneAxe()
{
return new StoneAxe();
}
// 配置Bean:steelAxe
@Bean(name="steelAxe")
public Axe steelAxe()
{
return new SteelAxe();
}
}

上面的配置文件中使用了Java配置类的三个常用注解。

注解 描述
@Configuration 用于修饰一个Java配置
@Bean 用于修饰一个方法,将该方法的返回值定义成容器中的一个Bean
@Value 用于修饰一个Field,用于为该Field配置一个值,相当于配置一个变量。

一旦使用了Java配置类来管理Spring容器中的Bean及其依赖关系,此时就需要使用如下方式来创建Spring容器:

1
2
ApplicationContext ctx = new
AnnotationConfigApplicationContext(AppConfig.class);

上面的AnnotationConfigApplicationContext类会根据**Java配置类**来创建Spring容器。不仅如此,该类还提供了一个register(Class)方法用于添加Java配置类。
使用Java配置类时,还有如下常用的注解。

注解 描述
@Import 修饰一个Java配置,用于向当前Java配置类中导入其他Java配置类。
@Scope 用于修饰一个方法,指定该方法对应的Bean的生命域。
@Lazy 用于修饰一个方法,指定该方法对应的Bean是否需要延迟初始化。
@DependsOn 用于修饰一个方法,指定在初始化该方法对应的Bean之前初始化所依赖的Bean

使用XML配置文件管理Bean和使用配置类管理Bean的对比

就普通用户习惯来看,还是使用XML配置文件管理Bean及其依赖关系更为方便,毕竟使用XML文件来管理Bean及其依赖关系是为了解耦。但这种Java配置类的方式又退回到Java代码耦合层次,只是将这种耦合集中到一个或多个Java配置类中。

为什么引入配置类来管理Bean

实际上, Spring提供@Configuration@Bean并不是为了完全取代XML配置,只是希望将它作为XML配置的一种补充。对于Spring框架的用户来说, Spring配置文件的”急剧膨胀”是一个让人头痛的点,因此Spring框架从2.0版本开始就不断地寻找各种对配置文件”减肥”的方法。
后面所介绍的各种注解也都是为了简化Spring配置文件而出现的,但由于注解引入时间较晚,因此在一些特殊功能的支持上,注解还不如XML强大。

使用XML还是使用注解

因此,在目前的多数项目中,

  • 要么**完全使用XML**配置方式管理Bean的配置,
  • 要么使用以注解为主、XML为辅的配置方式管理Bean的配置,想要完全放弃XML配置还是比较难的。

为什么会混用XML和注解

之所以会出现两者共存的情况,主要归结为三个原因:

  • 其一,目前绝大多数采用Spring进行开发的项目,几乎都是基于XML配置方式的, Spring在引入注解的同时,必须保证注解能够与XML和谐共存,这是前提;
  • 其二,由于注解引入较晚,因此功能也没有发展多年的XML强大,对于复杂的配置,注解还很难独当一面,在一段时间内仍然需要XML的配合才能解决问题;
  • 其三, SpringBean的配置方式与Spring核心模块之间是解耦的,因此,改变配置方式对Spring的框架自身是透明的。 Spring可以通过使用Bean后处理器( BeanPostProcessor)非常方便地增加对于注解的支持。

    以XML配置为主还是以Java类配置为主

    因此,在实际项目中可能会混合使用XML配置和Java类配置,在这种混合下存在一个问题:项目到底以XML配置为主,还是以Java类配置为主呢?

    以XML配置为主时

    如果以XML配置为主,就需要让XML配置能加载Java类配置。这并不难,只要在XML配置中增加如下代码即可:
    1
    2
    3
    4
    5
    <?xml version="1.0" encoding="GBK"?>
    <beans ....>
    ....
    <bean class="org.crazyit.app.config.AppConfig"/>
    </beans>
    由于以XML配置为主,因此应用创建Spring容器时,还是以这份XML配置文件为参数来创建ApplicationContext对象。那么Spring会先加载这份XML配置文件,再根据这份XML配置文件的指示去加载指定的Java配置类。

    如果以Java类配置为主

    如果以Java类配置为主,就需要让Java配置类能加载XML配置。这就需要借助于@)ImportResource注解,这个注解可修饰Java配置类,用于导入指定的XML配置文件。也就是在Java配置类上增加如下注解:
    1
    2
    3
    4
    5
    6
    7
    @Configuration
    // 导入XML配置
    @ImportResource("classpath:/beans.xml")
    public class AppConfig
    {
    ......
    }
    由于以Java类配置为主,因此应用创建Spring容器时,应以Java配置类为参数,通过创建AnnotationConfigApplicationContext对象作为Spring容器。那么Spring会先加载这个Java配置类,再根据这个Java配置类的指示去加载指定的XML配置文件。