什么是 FactoryBean?

工程 | Josh Long | 2011 年 8 月 9 日 | ...

在这篇文章中,我将探讨 Spring 的 org.springframework.beans.factory.FactoryBean<T> 接口。该接口的定义如下:


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

FactoryBean 是一种模式,用于将有趣的对象构建逻辑封装在类中。例如,它可以用于以可重用的方式编码复杂对象图的构建。通常,这用于构建具有许多依赖项的复杂对象。当构建逻辑本身高度易变且依赖于配置时,也可以使用它。FactoryBean 也可用于帮助 Spring 构建它本身无法轻松构建的对象。例如,为了注入从 JNDI 获取的 Bean 的引用,必须首先获取该引用。您可以使用 JndiFactoryBean 以一致的方式获取此引用。您可以将 FactoryBeangetObject() 方法的结果注入任何其他属性。

假设您有一个 Person 类,其定义如下:


public class Person { 
 private Car car ;
 private void setCar(Car car){ this.car = car;  }	
}

以及一个 FactoryBean,其定义如下:


public class MyCarFactoryBean implements FactoryBean<Car>{
  private String make; 
  private int year ;

  public void setMake(String m){ this.make =m ; }

  public void setYear(int y){ this.year = y; }

  public Car getObject(){ 
    // wouldn't be a very useful FactoryBean 
    // if we could simply instantiate the object!
    CarBuilder cb = CarBuilder.car();
	
    if(year!=0) cb.setYear(this.year);
    if(StringUtils.hasText(this.make)) cb.setMake( this.make ); 
    return cb.factory(); 
  }

  public Class<Car> getObjectType() { return Car.class ; } 

  public boolean isSingleton() { return false; }
}

您可以使用假设的 CarFactoryBean 这样连接 Car 实例:

 
<bean class = "a.b.c.MyCarFactoryBean" id = "car">
	<property name = "make" value ="Honda"/>
	<property name = "year" value ="1984"/>
</bean>
<bean class = "a.b.c.Person" id = "josh">
	<property name = "car" ref = "car"/>
</bean>

在此示例中,将传递 FactoryBeangetObject 方法的结果,而不是实际的 FactoryBean 本身。Spring 知道结果可以注入目标属性,因为它将查询 FactoryBeangetObjectType() 返回值以确定工厂对象的类型,然后它将检查该类型是否可以注入注入点。如果 FactoryBeanisSingleton() 方法返回 true,则 Spring 保留(但在实践中并不总是执行)缓存返回 Bean 的权利。

如果您使用的是 Spring 更新的(在我看来,更优雅的)基于 Java 的配置,那么您会发现它并不像您期望的那样工作。它仍然可以工作,但您必须在 Java 配置中显式地取消引用 FactoryBean 并自己调用 getObject(),如下所示:

 
// identical configuration in Java to the XML above			
@Configuration 
public class CarConfiguration { 

  @Bean 
  public MyCarFactoryBean carFactoryBean(){ 
	MyCarFactoryBean cfb = new MyCarFactoryBean();
	cfb.setMake("Honda");
	cfb.setYear(1984);
	return cfb;
  }

  @Bean
  public Person aPerson(){ 
	Person person = new Person();
	person.setCar( carFactoryBean().getObject());
	return person; 
  }	
}

请注意,本质上,在 Spring 中配置的所有 Bean 在运行时最终都位于同一位置。您可以使用 Java 配置定义 FactoryBean(如上所示),但随后可以在 XML 中使用工厂 Bean,就像使用在 XML 中定义的 FactoryBean 一样。

Spring FactoryBean 具有任何其他 Spring Bean 的所有其他特性,包括所有 Bean 在 Spring 容器中享有的生命周期钩子和服务(如 AOP)。

因此,如果您想在 FactoryBean 上的属性设置后但 getObject() 方法调用之前执行构建逻辑,那么您可以告诉 Spring 容器向您的 FactoryBean 提供回调。一种方法是实现 InitializingBean 接口。无论如何,这都会被调用。一个更面向 POJO 的替代方案是用 @PostConstruct 注解一个方法。在这种情况下,此方法将在 makeyear 属性都设置后被调用。您可以在对象构建完成之前但容器配置完成后使用此回调执行完整性检查。

 
 @PostConstruct 
 public void setup() throws Throwable { 
   // these methods throw an exception that 
   // will arrest construction if the assertions aren't met
   Assert.notNull(this.make, "the 'make' must not be null")	;
   Assert.isTrue(this.year > 0, "the 'year' must be a valid value"); 
 }

这里一个重要的要点是,存在于 Spring 容器中并享受生命周期钩子和容器服务的,是 FactoryBean,而不是工厂对象本身。返回的实例是短暂的——Spring 不知道您从 getObject() 返回了什么,并且不会尝试在其上执行任何生命周期钩子或任何其他操作。

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部