How to use the Spring FactoryBean? – 如何使用Spring FactoryBean?

最后修改: 2016年 12月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

There are two kinds of beans in the Spring bean container: ordinary beans and factory beans. Spring uses the former directly, whereas latter can produce objects themselves, which are managed by the framework.

在Spring的Bean容器中有两种Bean:普通Bean和工厂Bean。Spring直接使用前者,而后者可以自己产生对象,由框架来管理。

And, simply put, we can build a factory bean by implementing org.springframework.beans.factory.FactoryBean interface.

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

2. The Basics of Factory Beans

2.工厂Bean的基础知识

2.1. Implement a FactoryBean

2.1.实现一个FactoryBean

Let’s look at the FactoryBean interface first:

让我们先看一下FactoryBean接口。

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

Let’s discuss the three methods:

让我们来讨论这三种方法。

  • getObject() – returns an object produced by the factory, and this is the object that will be used by Spring container
  • getObjectType() – returns the type of object that this FactoryBean produces
  • isSingleton() – denotes if the object produced by this FactoryBean is a singleton

Now, let’s implement an example FactoryBean. We’ll implement a ToolFactory which produces objects of the type Tool:

现在,让我们来实现一个例子FactoryBean。我们将实现一个ToolFactory,它产生Tool类型的对象。

public class Tool {

    private int id;

    // standard constructors, getters and setters
}

The ToolFactory itself:

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
}

As we can see, the ToolFactory is a FactoryBean, which can produce Tool objects.

我们可以看到,ToolFactory是一个FactoryBean,它可以产生Tool对象。

2.2. Use FactoryBean With XML-based Configuration

2.2.使用FactoryBean和基于XML的配置

Let’s now have a look at how to use our ToolFactory.

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

We’ll start constructing a tool with XML-based configuration – factorybean-spring-ctx.xml:

我们将开始构建一个基于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>

Next, we can test if the Tool object is injected correctly:

接下来,我们可以测试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));
    }
}

The test result shows we manage to inject the tool object produced by the ToolFactory with the properties we configured in the factorybean-spring-ctx.xml.

测试结果显示,我们设法用我们在factorybean-spring-ctx.xml中配置的属性注入由ToolFactory产生的工具对象。

The test result also shows that the Spring container uses the object produced by the FactoryBean instead of itself for dependency injection.

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

Although the Spring container uses the FactoryBean‘s getObject() method’s return value as the bean, you can also use the FactoryBean itself.

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

To access the FactoryBean, you just need to add a “&” before the bean name.

要访问FactoryBean,你只需要在Bean名称前添加一个”&”。

Let’s try getting the factory bean and its factoryId property:

让我们试着获取工厂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. Use FactoryBean With Java-based Configuration

2.3.使用FactoryBean与基于Java的配置

Use FactoryBean with Java-based configuration is a little different with XML-based configuration, you have to call the FactoryBean‘s getObject() method explicitly.

在基于Java的配置中使用FactoryBean与基于XML的配置有点不同,你必须明确调用FactoryBeangetObject()方法。

Let’s convert the example in the previous subsection into a Java-based configuration example:

让我们把上一小节的例子转换为基于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();
    }
}

Then, we test if the Tool object is injected correctly:

然后,我们测试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));
    }
}

The test result shows the similar effect as the previous XML-based configuration test.

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

3. Ways to Initialize

3.初始化的方法

Sometimes you need to perform some operations after the FactoryBean has been set but before the getObject() method is called, like properties check.

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

You can achieve this by implementing the InitializingBean interface or using @PostConstruct annotation.

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

More details about using these two solutions have been introduced in another article: Guide To Running Logic on Startup in Spring.

关于使用这两种解决方案的更多细节已经在另一篇文章中介绍过了。Guide To Running Logic on Startup in Spring

4. AbstractFactoryBean

4、AbstractFactoryBean

Spring provides the AbstractFactoryBean as a simple template superclass for FactoryBean implementations. With this base class, we can now more conveniently implement a factory bean which creates a singleton or a prototype object.

Spring提供了AbstractFactoryBean作为FactoryBean实现的一个简单模板超类。有了这个基类,我们现在可以更方便地实现工厂Bean,它可以创建一个单子或原型对象。

Let’s implement a SingleToolFactory and a NonSingleToolFactory to show how to use AbstractFactoryBean for both singleton and prototype type:

让我们实现一个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
}

And now the nonsingleton implementation:

现在是非辛格尔顿的实现。

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
}

Also, the XML config for these factory beans:

还有,这些工厂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>

Now we can test if the Worker objects’ properties are injected as we expect:

现在我们可以测试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);
    }
}

As we can see from the tests, the SingleToolFactory produces singleton object, and the NonSingleToolFactory produces prototype object.

从测试中我们可以看到,SingleToolFactory产生了单子对象,而NonSingleToolFactory产生了原型对象。

Note that there’s no need to set singleton property in SingleToolFactory because, in AbstractFactory, singleton property’s default value is true.

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

5. Conclusion

5.结论

Using a FactoryBean can be a good practice to encapsulate complex construction logic or make configuring highly configurable objects easier in Spring.

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

So in this article, we introduced the basics of how to implement our FactoryBean, how to use it in both XML-based configuration and Java-based configuration, and some other miscellaneous aspects of FactoryBean, such as initialization of FactoryBean and AbstractFactoryBean.

所以在这篇文章中,我们介绍了如何实现我们的FactoryBean的基本知识,如何在基于XML的配置和基于Java的配置中使用它,以及FactoryBean的一些其他杂项,例如FactoryBeanAbstractFactoryBean的初始化。

As always, the complete source is over on GitHub.

一如既往,完整的源代码在GitHub上