一、介绍
一般情况下,Spring通过反射机制利用<bean>
的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>
中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean
的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>
的形式
FactoryBean
是一个接口,当在IOC容器中的Bean实现了 FactoryBean
后,不同于普通Bean的是:通过getBean(String BeanName)
获取到的Bean对象并不是FactoryBean
的实现类对象,而是这个实现类中的getObject()
方法返回的对象。要想获取FactoryBean
的实现类,就要getBean(&BeanName)
,在BeanName之前加上&。即容器中会有两个 Bean。
简单来说,就是 FactoryBean
可以实现复杂 Bean 的创建。
虽然 @Bean
也可以实现复杂 Bean 的创建,但是一些批量创建 Bean 的操作还是必须由 FactoryBean
实现
FactoryBean应该是配合BeanDefiniton和Scan注解达到批量生产某个指定包下的bean,而不用一个一个手动创建这些bean的目的
FactoryBean的好处是spring与第三方jar包集成时,便于使用ioc注入bean,比如Mybatis的SqlSessionFactoryBean。Spring整合Mybatis也是通过实现FactoryBean,然后批量将BeanDefiniton注册到spring中管理
个人理解,FactoryBean 是很古早的一个类了,和 xml 初始化配套的东西,在 xml 时代配置文件能做的工作有限,只能通过工厂类初始化复杂的 bean。但是现在 java configuration 时代 java 本身就已经逻辑完备了,只使用 @Bean 就可以满足复杂 bean 的初始化需求。所以现在看来 FactoryBean 可以算作一个历史遗留的备选方案吧
二、接口
由BeanFactory中使用的对象实现的接口,这些对象本身就是单个对象的工厂。如果一个bean实现了这个接口,那么它将被用作要公开的对象的工厂,而不是直接用作将自己公开的bean实例。
注意:实现此接口的bean不能作为普通bean使用。FactoryBean是以bean风格定义的,但是为bean引用公开的对象(getObject())始终是它所创建的对象。
FactoryBeans可以支持单例和原型,可以按需惰性地创建对象,也可以在启动时快速创建对象。SmartFactoryBean接口允许公开更细粒度的行为元数据。
这个接口在框架本身中被大量使用,例如AOP的org.springframework.aop.framework.ProxyFactoryBean或org.springframework.jndi.JndiObjectFactoryBean。它也可以用于定制组件;但是,这只在基础结构代码中常见。
FactoryBean是一个编程契约。实现不应该依赖于注释驱动的注入或其他反射工具。getObjectType() getObject()调用可能在引导过程的早期到达,甚至在任何后处理器设置之前。如果需要访问其他bean,则实现BeanFactoryAware并以编程方式获取它们。
最后,FactoryBean对象参与了包含BeanFactory的bean创建的同步。除了在FactoryBean本身(或类似)内进行惰性初始化之外,通常不需要内部同步。
public interface FactoryBean<T> {
/**
* 可以在beandefinition上设置的属性的名称,这样当不能从工厂bean类中推导出对象类型时,工厂bean就可以标识他的类型。
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
* 返回这个工厂管理的对象的实例(可能是共享的,也可能是独立的)。
* 与BeanFactory一样,这允许同时支持单例和原型设计模式。
*
* 如果这个FactoryBean在调用时还没有完全初始化(例如,因为它涉及到循环引用中),
* 则抛出相应的FactoryBeanNotInitializedException。
*
* 从Spring 2.0开始,FactoryBeans允许返回空对象。
* 工厂会将此视为正常值使用;在这种情况下,它将不再抛出FactoryBeanNotInitializedException。
* 现在鼓励FactoryBean实现在适当的时候抛出FactoryBeanNotInitializedException。
*/
@Nullable
T getObject() throws Exception;
/**
* 返回这个FactoryBean创建的对象类型,如果事先不知道,则返回null。
*
* 这允许在不实例化对象的情况下检查特定类型的bean,例如在自动装配时。
*
* 在创建单例对象的实现中,该方法应该尽量避免创建单例对象;它应该提前估计类型。
* 对于原型,在这里返回有意义的类型也是可取的。
*
* 这个方法可以在FactoryBean完全初始化之前调用。它不能依赖于初始化过程中创建的状态;
* 当然,如果可用,它仍然可以使用这种状态。
*
* 注意:自动装配将简单地忽略这里返回null的FactoryBeans。
* 因此,强烈建议使用FactoryBean的当前状态正确地实现此方法。
*/
@Nullable
Class<?> getObjectType();
/**
* 这个工厂管理的对象是单例对象吗?也就是说,getObject()是否总是返回相同的对象(可以缓存的引用)?
*
* 注意:如果FactoryBean指示保存一个单例对象,
* 那么从getObject()返回的对象可能会被所属的BeanFactory缓存。
* 因此,除非FactoryBean总是公开相同的引用,否则不要返回true。
*
* FactoryBean本身的单例状态通常由拥有它的BeanFactory提供;通常,它必须被定义为单例。
*
* 注意:此方法返回false并不一定表示返回的对象是独立的实例。
* 扩展SmartFactoryBean接口的实现可以通过其SmartFactoryBean.
* isprototype()方法显式地指示独立实例。如果isSingleton()实现返回false,
* 则不实现此扩展接口的普通FactoryBean实现被简单地假定总是返回独立实例。
*
* 默认实现返回true,因为FactoryBean通常管理一个单例实例。
*/
default boolean isSingleton() {
return true;
}
}
三、基本使用
1、注解方式
@Component(value = "msgEntity")
public class TestFactoryBean implements FactoryBean<MsgEntity> {
@Override
public MsgEntity getObject() throws Exception {
MsgEntity msgEntity = new MsgEntity();
msgEntity.setId(1L);
return msgEntity;
}
@Override
public Class<?> getObjectType() {
return MsgEntity.class;
}
}
测试
@Autowired
private ApplicationContext applicationContext;
@Test
public void test13(){
// 获取 FactoryBean 实现类
Object mapperFactoryBean = applicationContext.getBean("&msgEntity");
System.out.println(mapperFactoryBean);
// 获取 FactoryBean 实现类 getObject() 方法产生的对象
Object mapperFactoryBean2 = applicationContext.getBean("msgEntity");
System.out.println(mapperFactoryBean2);
Map<String, TestFactoryBean> beansOfType = applicationContext.getBeansOfType(TestFactoryBean.class);
System.out.println(beansOfType);
Map<String, MsgEntity> beansOfType2 = applicationContext.getBeansOfType(MsgEntity.class);
System.out.println(beansOfType2);
}
输出
io.renren.modules.test.TestFactoryBean@758ac46
MsgEntity(id=1)
{&msgEntity=io.renren.modules.test.TestFactoryBean@758ac46}
{msgEntity=MsgEntity(id=1)}
2、Spring配置文件方式
<!-- 如果Class指定的类型是 实现了FactoryBean,那么通过id值获取到这个对象 -->
<bean id="msgEntity" class="com.rewind.factoryebean.TestFactoryBean" />
四、MapperFactoryBean
Mybatis
是通过 MapperFactoryBean
将 mapper 动态生成的代理类添加到工厂容器中
Spring框架启动时:
会扫描指定路径下的 Mapper
接口,将 Mapper
接口转换为 Spring
中的 BeanDefinition
对象,并且beanClass
属性为 MapperFactoryBean
,Spring
框架在所有的 Bean
配置转换为 BeanDefinition
对象后,就会根据 BeanDefinition
对象的 beanClass
属性创建Bean的实例。
Spring框架启动后:
每个Mapper接口创建一个 MapperFactoryBean
对象,当我们通过Mapper接口获取Bean时,获取到的是 MapperFactoryBean
对象的 getObject()
方法返回的对象。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
public boolean isAddToConfig() {
return this.addToConfig;
}
}