什么是 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() 返回值来确定工厂化对象的类型,然后检查该类型是否可以注入到注入点。Spring 保留——但在实践中并不总是行使——在 FactoryBeanisSingleton() 方法返回 true 时缓存返回的 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 FactoryBeans 拥有任何其他 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 社区所有即将举行的活动。

查看所有