Spring FactoryBean详解

1.概述

Spring 容器中有两种bean:普通bean和工厂bean。Spring直接使用前者,FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。

一般情况下,Spring通过反射机制利用的class属性指定的实现类来实例化bean 。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到更好的效果。Spring为此提供了一个org.Springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。

FactoryBean接口对于Spring框架来说占有重要的地位,Spring 自身就提供了很多FactoryBean的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring 3.0 开始, FactoryBean开始支持泛型,即接口声明改为FactoryBean 的形式。

2.应用场景

FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置和基于Java配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。

3.FactoryBean接口

让我们首先看一下FactoryBean接口:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

此接口中定义了以下3个方法,前2个方法需要我们去实现:

  • getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器的单实例缓存池中。getObject方法内部由开发者自己去实现对象的创建,然后将创建好的对象返回给Spring容器。
  • getObjectType():返回FactoryBean创建的bean类型。
  • isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype,如果返回false,表示由这个FactoryBean创建的对象是多例的,那么我们每次从容器中getBean的时候都会去重新调用FactoryBean中的getObject方法获取一个新的对象。若返回true,表示创建的对象是单例的,那么我们每次从容器中获取这个对象的时候都是同一个对象。

当配置文件中配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。如果要获取FactoryBean对象,可以在id前面加一个&符号来获取。

4.应用案例

很多开源项目在集成Spring 时都使用到FactoryBean,比如 MyBatis3 提供 mybatis-spring项目中的 org.mybatis.spring.SqlSessionFactoryBean

<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="trade" />
        <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
    </bean>

org.mybatis.spring.SqlSessionFactoryBean 如下:

package org.mybatis.spring;

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
        ......
}

5.FactoryBean实例

2.1 实现一个FactoryBean

现在,让我们实现一个示例FactoryBean。我们将实现一个ToolFactory,它生成Tool类型的对象:

public class Tool {
 
    private int id;
 
    // standard constructors, getters and setters
}

ToolFactory

public class ToolFactory implements FactoryBean<Tool> {
 
    private int factoryId;
    private int toolId;
 
    @Override
    public Tool getObject() throws Exception {
        return new Tool(toolId);
    }
 
    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }
 
    @Override
    public boolean isSingleton() {
        return false;
    }
 
    // standard setters and getters
}

如我们所见,ToolFactory是一个FactoryBean,可以生成Tool对象。

2.2 FactoryBean与基于XML的配置一起使用

现在让我们看看如何使用ToolFactory

构建基于XML的配置– factorybean-spring-ctx.xml

<beans ...>
    <bean id="tool" class="com.baeldung.factorybean.ToolFactory">
        <property name="factoryId" value="9090"/>
        <property name="toolId" value="1"/>
    </bean>
</beans>

接下来,我们可以测试Tool对象是否正确注入:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-spring-ctx.xml" })
public class FactoryBeanXmlConfigTest {
    @Autowired
    private Tool tool;
 
    @Test
    public void testConstructWorkerByXml() {
        assertThat(tool.getId(), equalTo(1));
    }
}

尽管Spring容器使用FactoryBeangetObject()方法的返回值作为Bean,但是您也可以使用FactoryBean本身。

要访问FactoryBean,您只需要在bean名称之前添加“&”即可。

让我们尝试获取FactoryBean及其factoryId属性:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-spring-ctx.xml" })
public class FactoryBeanXmlConfigTest {
 
    @Resource(name = "&tool")
    private ToolFactory toolFactory;
 
    @Test
    public void testConstructWorkerByXml() {
        assertThat(toolFactory.getFactoryId(), equalTo(9090));
    }
}

2.3 FactoryBean与基于Java的配置一起使用

在基于Java的配置中使用FactoryBean与基于XML的配置有所不同,您必须显式调用FactoryBean的*getObject()*方法。

让我们将前面小节中的示例转换为基于Java的配置示例:

@Configuration
public class FactoryBeanAppConfig {
  
    @Bean(name = "tool")
    public ToolFactory toolFactory() {
        ToolFactory factory = new ToolFactory();
        factory.setFactoryId(7070);
        factory.setToolId(2);
        return factory;
    }
 
    @Bean
    public Tool tool() throws Exception {
        return toolFactory().getObject();
    }
}

然后,我们测试Tool对象是否正确注入:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FactoryBeanAppConfig.class)
public class FactoryBeanJavaConfigTest {
 
    @Autowired
    private Tool tool;
  
    @Resource(name = "&tool")
    private ToolFactory toolFactory;
 
    @Test
    public void testConstructWorkerByJava() {
        assertThat(tool.getId(), equalTo(2));
        assertThat(toolFactory.getFactoryId(), equalTo(7070));
    }
}

测试结果显示出与以前的基于XML的配置测试相似的效果。

6.初始化方式

有时,您需要在设置FactoryBean之后但在调用*getObject()*方法之前执行一些操作,例如属性检查。

您可以通过实现InitializingBean接口或使用*@PostConstruct*注解来实现。

7. AbstractFactoryBean

Spring为FactoryBean实现提供了一个超类模板(AbstractFactoryBean)。有了这个模板,我们可以更方便地实现一个工厂bean,它创建一个单例或一个原型对象。

让我们实现SingleToolFactoryNonSingleToolFactory展示如何使用AbstractFactoryBean的singleton和prototype类型:

public class SingleToolFactory extends AbstractFactoryBean<Tool> {
 
    private int factoryId;
    private int toolId;
 
    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }
 
    @Override
    protected Tool createInstance() throws Exception {
        return new Tool(toolId);
    }
 
    // standard setters and getters
}

非单例实现:

public class NonSingleToolFactory extends AbstractFactoryBean<Tool> {
 
    private int factoryId;
    private int toolId;
 
    public NonSingleToolFactory() {
        setSingleton(false);
    }
 
    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }
 
    @Override
    protected Tool createInstance() throws Exception {
        return new Tool(toolId);
    }
 
    // standard setters and getters
}

另外,这些工厂bean的XML配置:

<beans ...>
 
    <bean id="singleTool" class="com.baeldung.factorybean.SingleToolFactory">
        <property name="factoryId" value="3001"/>
        <property name="toolId" value="1"/>
    </bean>
 
    <bean id="nonSingleTool" class="com.baeldung.factorybean.NonSingleToolFactory">
        <property name="factoryId" value="3002"/>
        <property name="toolId" value="2"/>
    </bean>
</beans>

测试是否按预期注入了Worker对象的属性:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-abstract-spring-ctx.xml" })
public class AbstractFactoryBeanTest {
 
    @Resource(name = "singleTool")
    private Tool tool1;
  
    @Resource(name = "singleTool")
    private Tool tool2;
  
    @Resource(name = "nonSingleTool")
    private Tool tool3;
  
    @Resource(name = "nonSingleTool")
    private Tool tool4;
 
    @Test
    public void testSingleToolFactory() {
        assertThat(tool1.getId(), equalTo(1));
        assertTrue(tool1 == tool2);
    }
 
    @Test
    public void testNonSingleToolFactory() {
        assertThat(tool3.getId(), equalTo(2));
        assertThat(tool4.getId(), equalTo(2));
        assertTrue(tool3 != tool4);
    }
}

正如我们从测试看出,SingleToolFactory生成单实例,而NonSingleToolFactory生成原型对象。

请注意,无需在SingleToolFactory中设置singleton属性,因为在AbstractFactory中,singleton属性的默认值为true

8.结论

使用FactoryBean可以封装复杂的构造逻辑,或者使Spring中配置高度可配置的对象更容易,这是一个很好的实践。

因此,在本文中,我们介绍了如何实现FactoryBean的基础知识,如何在基于xml的配置和基于java的配置中使用它,以及FactoryBean的其他一些用法,比如FactoryBean和AbstractFactoryBean的初始化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值