本文继续之前的2篇文章(BeanPostProcessor和BeanFactoryPostProcessor)介绍Ioc容器的功能扩展。
FactoryBean是用来构造Bean的接口。常规情况下向容器添加一个Bean只需要像下面这样通过XML的配置或注解直接引入这个类即可:
<bean id="a" class="x.y.z.A">
<property name="setter" value="1" />
</bean>
class A{}
但是某些情况下我们需要动态的装载一个复的Bean,此时可以使用FactoryBean来动态装载一个Bean。FactoryBean字面上看就知道它是一个Bean,但是有Factory的功能(工厂模式)。
FactoryBean的使用和之前介绍的Processor 一样,实现一个接口,然后设置为一个Spring的Bean即可:
class MyFactory implements FactoryBean{
public Object getObject() throws Exception {
return null;
}
public Class getObjectType() {
return null;
}
}
下面通过一个适配器的例子来说明FactoryBean的使用,文中的代码仅用于示例,可执行源码请移步 https://gitee.com/chkui-com/spring-core-sample 中的 chkui.springcore.example.xml.factorybean包。
例子是使用适配器模式对对应的资源进行解码,执行一下3步:
- 容器启动之后会加载一个密文资源类,可能是Base64的编码,也可能是UrlBase64的编码,根据配置来确定。
- FactoryBean会根据资源类型向容器添加一个解码的适配器。
- 最后用适配器解码输出。
例子的代码结构如下:
factorybean
--BeanFactoryApp.java main方法
--AdapterFactory.java 一个FactoryBean,用于生成适配器Bean
--entity
----Text.java 编码资源类的接口
----Base64Entity.java Base64编码
----UrlBase64Entity.java urlBase64编码
--adapter
----DecodeAdapter.java 解码适配器接口
----Base64Adapter.java Base64的解码适配器
----UrlBase64Adapter.java UrlBase64的解码适配器
另外配置文件在 resources/xml/factorybean/config.xml:
<beans>
<!-- Base64编码 -->
<bean class="chkui.springcore.example.xml.factorybean.entity.Base64Entity">
<constructor-arg value="一串加密的文字。URLBase64和Base64的区别是调整了可以用于URL的符号,例如+替换为-。"/>
</bean>
<!-- UrlBase64编码 -->
<!-- <bean class="chkui.springcore.example.xml.factorybean.entity.UrlBase64Entity">
<constructor-arg value="一串加密的文字。URLBase64和Base64的区别是调整了可以用于URL的符号,例如+替换为-。"/>
</bean> -->
<bean id="adapter" class="chkui.springcore.example.xml.factorybean.AdapterFactory" />
</beans>
Base64Entity和UrlBase64Entity是2个资源类,分别用Base64和UrlBase64对字符串进行编码,通过配置来管理。下面是Text和Base64Entity的代码:
package chkui.springcore.example.xml.factorybean.entity;
//文本资源接口
public interface Text {
//定义资源类型,目前支持Base64和UrlBase642种加密编码文件
public static enum Type{
Base64,
UrlBase64
}
//获取资源编码类型
Type getType();
//获取编码的密文
String getCipher();
}
package chkui.springcore.example.xml.factorybean.entity;
public class Base64Entity implements Text {
private String cipher;
public Base64Entity(String text) {
this.cipher = Base64.getEncoder().encodeToString(text.getBytes());
}
public Type getType() {
return Text.Type.Base64;
}
public String getCipher() {
return cipher;
}
}
然后我们根据不同的资源定义了不同的适配器来解码,下面是适配器接口和一个实现类——DecodeAdapter、Base64Adapter:
package chkui.springcore.example.xml.factorybean.adapter;
//加密编码文件解码适配器
public interface DecodeAdapter {
//获取解码之后的明文
String getPlain();
}
package chkui.springcore.example.xml.factorybean.adapter;
public class Base64Adapter implements DecodeAdapter {
private String cipher;
public Base64Adapter(String cipher){
this.cipher = cipher;
}
public String getPlain() {
return new String(Base64.getDecoder().decode(cipher));
}
}
最后是核心的FactoryBean——AdapterFactory,他的作用是根据当前向IoC添加的资源类型来确定启用哪个适配器。AdapterFactory继承了BeanFactoryAware以便获得BeanFactory实例:
public class AdapterFactory implements FactoryBean<DecodeAdapter>, BeanFactoryAware {
private Text text;
private volatile DecodeAdapter adapter;
@Override
public DecodeAdapter getObject() throws Exception {
//根据IoC中的资源类型选择适配器,懒加载模式
return lazyLoadAdapter();
}
@Override
public Class<DecodeAdapter> getObjectType() {
return DecodeAdapter.class;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.text = beanFactory.getBean(Text.class);
}
private DecodeAdapter lazyLoadAdapter() {
if (null == adapter) {
synchronized (AdapterFactory.class) {
if (null == adapter) {
switch (text.getType()) {
case UrlBase64:
adapter = new UrlBase64Adapter(text.getCipher());
break;
case Base64:
default:
adapter = new Base64Adapter(text.getCipher());
break;
}
}
}
}
return this.adapter;
}
}
lazyLoadAdapter方法实现了适配的过程——根据不同的编码类型返回不同的适配器。最后运行容器:
package chkui.springcore.example.xml.factorybean;
public class BeanFactoryApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("xml/factorybean/config.xml");
Text text = context.getBean(Text.class);
System.out.println("密文:" + text.getCipher());
System.out.println("编码类型:" + text.getType());
DecodeAdapter decode = context.getBean(DecodeAdapter.class);
System.out.println("明文:" + decode.getPlain());
}
//通过符号规则获取工厂Bean
private static void nameSymbol(ApplicationContext context) {
Object adapter = context.getBean("adapter");//获取实际Bean
System.out.println("adapterClass :" + adapter.getClass().getName());
adapter = context.getBean("&adapter");//获取实际工厂Bean
System.out.println("adapterClass :" + adapter.getClass().getName());
}
}
实际上,Spring的所有预设Bean都是通过FactoryBean实现的,现在大概有50多个Spring官方实现的FactoryBean。
注意nameSymbol方法中的代码和BeanFactory的配置——<bean id="adapter" class="chkui.springcore.example.xml.factorybean.AdapterFactory" />。如果为BeanFactory指定了ID或别名,那么通过ID获取到的是工厂生成Bean而不是这个工厂本身。但是可以通过在之前增加"&"符号来告诉IoC获取BeanFactory本身。