1. 概述

Spring容器中有两种类型的bean:普通bean和工厂bean。Spring直接使用前者,而后者可以自己创建对象,这些对象由框架管理。

简单来说,我们可以通过实现org.springframework.beans.factory.FactoryBean接口来构建一个工厂bean。

2. 工厂bean的基本原理

2.1. 实现FactoryBean

首先来看看FactoryBean接口:

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

接下来讨论三个方法:

  • getObject() - 返回工厂产生的对象,这就是Spring容器将使用的对象。
  • getObjectType() - 返回这个FactoryBean生产的对象类型。
  • isSingleton() - 标记由这个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. 通过XML配置使用FactoryBean

现在来看看如何使用我们的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>

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

@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));
    }
}

测试结果显示,我们成功地将ToolFactory产生的工具对象注入到XML配置中,并使用了我们设置的属性。

测试结果还显示,Spring容器使用FactoryBean产生的对象进行依赖注入,而不是自己。

尽管Spring容器使用FactoryBeangetObject()方法的返回值作为bean,但也可以直接使用FactoryBean

**要访问FactoryBean,只需在bean名称前添加“&”。

让我们尝试获取工厂bean及其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. 通过Java配置使用`FactoryBean`**

使用Java配置的`FactoryBean`与XML配置略有不同,需要显式调用`FactoryBean`的`getObject()`方法。

让我们将上一节中的例子转换为Java配置示例:

```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();
    }
}

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

@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配置测试有类似的效果。

3. 初始化方式

有时,您可能需要在FactoryBean设置后但在getObject()方法被调用之前执行一些操作,例如检查属性。可以通过实现InitializingBean接口或使用@PostConstruct注解来实现这一点。

有关这两种解决方案的更多详细信息,请参阅另一篇文章:在Spring中运行启动时逻辑指南

4. AbstractFactoryBean

Spring提供了AbstractFactoryBean作为FactoryBean实现的简单基类。有了这个基础类,我们现在可以更方便地实现一个工厂bean,用于创建单例或原型对象。

让我们实现一个SingleToolFactory和一个NonSingleToolFactory,展示如何使用AbstractFactoryBean处理单例和原型类型:

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中设置单例属性,因为在AbstractFactory中,单例属性的默认值是true

5. 总结

使用FactoryBean是一种很好的实践,可以封装复杂的构造逻辑或在Spring中使高度可配置的对象配置更简单。

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

如往常一样,完整的源代码可以在GitHub上找到。