1. 概述
Spring Bean 容器中存在兩種類型的 Bean:普通 Bean 和工廠 Bean。Spring 直接使用普通 Bean,而工廠 Bean 則可以生成對象本身,這些對象由框架管理。
簡單來説,我們可以通過實現 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
}工具工廠本身:
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 的配置來構建工具,該 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));
}
}測試結果表明,我們成功地將由 ToolFactory 產生的工具對象,以及我們配置的屬性注入到其中。
測試結果還表明,Spring 容器使用由 FactoryBean 產生的對象,而不是自身進行依賴注入。
儘管 Spring 容器使用 FactoryBean 的 getObject() 方法的返回值作為 Bean,您也可以使用 FactoryBean 本身。
要訪問 FactoryBean,您只需要在 Bean 名稱前添加 "&" 即可。
讓我們嘗試獲取 factory 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. 使用 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 的先前配置測試相似。
3. 初始化方式
有時,在 <em style="font-style: italic;">FactoryBean</em> 被設置後,但在調用 <em style="font-style: italic;">getObject()</em> 方法之前,需要執行一些操作,例如屬性檢查。
可以通過實現 <em style="font-style: italic;">InitializingBean</em> 接口或使用 <em style="font-style: italic;">@PostConstruct</em> 註解來實現。
關於使用這兩種解決方案的更多詳細信息,請參閲另一篇文章:《在 Spring 中運行啓動時邏輯指南》。
4. 抽象工廠Bean (AbstractFactoryBean)
Spring 提供 AbstractFactoryBean 作為 FactoryBean 實現的簡單模板超類。藉助該基類,我們可以更方便地實現一個工廠 Bean,該 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
}<p>現在我們來介紹一下nonsingleton的實現:</p>
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 的初始化。