领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在我上一篇文章中,我介绍了基本的 FactoryBean 是什么。虽然 FactoryBeans 很重要——了解它们的作用可以帮助你更有效地驾驭框架——但就 Spring 3.0 和即将发布的 Spring 3.1 而言,它们已不再是推荐的做法。
FactoryBean 的核心思想是隐藏对象的构造——因为它非常复杂,或者因为它不能仅仅使用 Spring 容器常用的以构造函数为中心的典型方法来实例化(也许它需要被查找?也许它需要一个静态注册方法?)。Spring 在 XML 格式中也支持 factory-method 属性。Java 配置方法提供了一个在概念上相似(实际上,结果相同)的替代方案,但具有更简洁、类型安全的选择。
Spring 3.0 引入了 Java 配置,它允许你使用 Java 定义 bean。例如,要在 XML 中将一个普通的 javax.sql.DataSource 注册到 Spring,你很可能会委托给一个属性文件来获取敏感的配置信息(如数据库密码),然后使用 Spring 来实例化 javax.sql.DataSource,如下所示:
<beans ...>
<context:property-placeholder location = "ds.properties" />
<bean id = "ds" class = "a.b.c.MySqlDataSource">
<property name = "user" value = "${ds.user}"/>
<property name = "password" value = "${ds.password}"/>
</bean>
</beans>
这是一个简单的 bean,自然地转化为 Java 配置。它看起来会是这样:
import a.b.c.* ;
@Configuration
@PropertySource("ds.properties")
public class MyConfiguration {
@Inject private Environment env ;
@Bean public MySqlDataSource ds(){
MySqlDataSource ds = new MySqlDataSource () ;
ds.setUser( env.getProperty("ds.user") );
ds.setPassword( env.getProperty("ds.password"));
return ds;
}
}
这样做的优点在于,您可以在方法内自由地进行任何您想做的操作。返回值会被注册到 Spring 容器中。方法的名称(ds)用于设置 bean 的名称。为了正确构造此对象,您所做的任何事情都由您决定——您不再受限于 Spring 根据 XML 配置所能实例化的对象的范围。这样做更自然——用 Java 保证没有错别字比用 XML 容易得多。所以,从中可以得出结论,Java 配置在代码行数方面大致相当,但功能更强大,概念上也更自然。
随着这些限制的解除,FactoryBean 的价值开始减弱。毕竟,如果 FactoryBean 所做的只是以一种新颖或独特的方式编码构造逻辑,那么为什么不能在 Java 配置方法内部完成这一切呢?让我们回顾一下上一篇博文中的例子,一个用于创建 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; }
}
在这个例子中,我们只在有值可设置时才设置值。所以,我们进行了一些处理来确保我们有值。这段代码很糟糕,因为它有很多不同的执行路径,但它并不特别新颖。我们都是成年人,我们可以自己做这些事情。让我们抛弃 FactoryBean,直接使用 Java 配置来替换它,从而定义一个 Car。同样,我们碰巧知道需要什么配置,所以我们不必在代码中重复 null 检查。
@Bean public Car honda (){
return CarBuilder.car()
.setYear( 1984 )
.setMake("Honda")
.factory();
}
不错!我们不再需要复杂的 FactoryBean,并且拥有了一个可用的 bean 定义。如果我们想使其可重用,我们也可以简单地创建一个工厂方法,如下所示:
// presumably exposed from some place where other configuration classes can reuse it.
public Car buildCar (int year, String make){
CarBuilder cb = CarBuilder.car();
if (year!=0) cb.setYear( year);
if (StringUtils.hasText( make)) cb.setMake( make);
return cb.factory ();
}
...
// now the Spring definition itself is even simpler, <em>and</em> it's reusable!
@Bean public Car honda () {
return buildCar(1984, "Honda") ;
}
在 Spring 3.1 中,Spring 在许多地方提供了 FactoryBean 的 Builder 替代方案。Builder 作为一种模式,在概念上与 FactoryBean 相似。但在实践中,它们通常像上面演示的 CarBuilder 一样暴露。它们通常是可链式的——一个方法返回 this,因此后续的调用不需要取消引用对象,它们可以继续链式调用。此外,Builder 通常会进行我先前代码中强制执行的 null 指针检查。因此,一个正确重写的 CarBuilder 对象用法可能如下所示:
@Bean public Car honda (){
return CarBuilder.car ()
// doesn't matter if the parameters are null -
// it'll validate in the factory() method
.setYear( 1984 )
.setMake( "Honda" )
.factory ();
}
在 3.1 版本中,Builder 提供比 Spring 的 FactoryBean 更流畅的体验的一个绝佳示例是新的 Hibernate 3 SessionFactoryBuilder,其用法如下所示:
@Configuration
@EnableTransactionManagment
public class ServiceConfiguration {
@Bean public javax.sql.DataSource dataSource (){ ... }
@Bean public SessionFactory hibernate3SessionFactory(){
return new AnnotationSessionFactoryBuilder()
.setDataSource(dataSource())
// you could do this:
//.setAnnotatedClasses( Customer.class, LineItem.class, Order.class )
// or simply scan a package where your entities live
.setAnnotatedPackages( Customer.class.getPackage().getName ())
.buildSessionFactory ();
}
}
等效的 FactoryBean(当然仍然存在)现在实际上委托给这些 builder 类!