7.8.3 容器中的工厂Bean

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的一个重要工具类.