如何使用Spring FactoryBean?

1.概述
 
Spring bean容器中有两种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 {
     
    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
  我们将基于 factorybean-spring-ctx.xml  开始构建一个Tool:
 
     
  1. ...>
  2.     id="tool" class="com.baeldung.factorybean.ToolFactory">
  3.         name="factoryId" value="9090"/>
  4.         name="toolId" value="1"/>
  5.    
接下来,我们可以测试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));
    }
}  

测试结果表明,我们使得 ToolFactory生成的Tool对象注入到了  Factorybean-spring-ctx.xml中配置的属性中。
试验结果还表明,Spring容器使用FactoryBean生成的对象而不是它本身用于依赖注入。

虽然Spring容器使用FactoryBean的getObject()方法的返回值作为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的配置示例:
@Configuration
public class FactoryBeanAppConfig {
  
    @Bean(name = "tool")
    public ToolFactory toolFactory() {
        ToolFactory factory = new ToolFactory();
        factory.setFactoryId(7070);
        factory.setToolId(2);
        return factory;
    }
}  
然后,我们测试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的配置测试有类似的效果。

3.初始化方式
 
有时我们需要在FactoryBean设置完成之后,调用getObject()方法之前执行一些操作,例如属性检查。
 我们 可以通过实现InitializingBean 接口或使用@PostConstruct 注解来实现此目的。
  有关使用这两个解决方案的更多详细信息,请参见另一篇文章:  Spring中启动时运行逻辑指南。

4. AbstractFactoryBean
 
Spring提供了AbstractFactoryBean作为 实现 FactoryBean的简单模板父类。使用这个基类,我们现在可以更方便地实现一个创建单例或原型对象的工厂bean。
  我们实现一个SingleToolFactory和一个NonSingleToolFactory来显示如何使用AbstractFactoryBean来为单例和原型类型:
public class SingleToolFactory extends AbstractFactoryBean {
     
    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 {
     
    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配置:
 
     
  1. ...>
  2. id="singleTool" class="com.baeldung.factorybean.SingleToolFactory">
  3. name="factoryId" value="3001"/>
  4. name="toolId" value="1"/>
  5. id="nonSingleTool" class="com.baeldung.factorybean.NonSingleToolFactory">
  6. name="factoryId" value="3002"/>
  7. name="toolId" value="2"/>
现在我们可以测试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。

5 结论
 
在Spring中使用FactoryBean可以很好地封装复杂的构造逻辑,或者使 配置 高度可配置的对象变得更加容易。
因此,在这篇文章中,我们介绍了如何实现我们自己的FactoryBean的 基础知识 ,如何基于XML的配置和基于Java的配置使用 FactoryBean ,以及一些其他使用FactoryBean的地方,如初始化的FactoryBean和AbstractFactoryBean。
 
一如以往,该GitHub项目中提供了该示例的完整源代码。







你可能感兴趣的:(Spring)