先行一步
VMware 提供培训和认证,助您加速进步。
了解更多更新:此博客文章中描述的 FeatureSpecification 功能已在 Spring Framework 3.1 M2 中移除,取而代之的是 @Enable* 注解。有关更多信息,请参阅3.1 M2 公告。
本系列的早期文章中,我提到了如何将新的 @Profile 注解与 @Configuration 类结合使用,以利用 Spring 的Bean 定义 Profile。今天,我们将探讨 Spring 3.1 中基于代码的配置领域中一个全新的补充:FeatureSpecification 类及其相关支持。
我为这篇文章准备了一个示例项目。请访问https://github.com/cbeams/spring-3.1-featurespec 并按照 README 中的说明进行操作。
到 @BeanSpring 3.0 中添加的对 @Configuration 类的支持本质上提供了一种用 Java 而非 XML 编写 Bean 定义的机制。例如:
/src/main/com/bank/config/xml/transfer-service-config.xml
<beans>
<bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<!-- 'dataSource' and other bean definitions -->
</beans>
可以转换为以下 @Configuration 类:
/src/main/com/bank/config/xml/TransferServiceConfig.java
@Configuration
public class TransferServiceConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource());
}
@Bean
public DataSource dataSource() {
// create, configure and return the DataSource ...
}
}
而 XML 配置的引导方式如下:
new GenericXmlApplicationContext("com/bank/config/xml/transfer-service-config.xml");
@Configuration 的引导方式类似,但以类作为输入:
new AnnotationConfigApplicationContext(TransferServiceConfig.class);
这种方法运作良好,但它仅限于表达单个 Bean 定义——迄今为止,在 @Configuration 世界中没有等同于 Spring XML 命名空间的支持。Spring 的 XML 命名空间,例如 context:*/ 和 tx:*/,允许以非常简洁的方式实现大量的配置。例如,正如许多用户所知,在 XML 中启用 Spring 的注解驱动事务管理只需一行:
/src/main/com/bank/config/xml/transfer-service-config.xml
<tx:annotation-driven transaction-manager="txManager"/>
在底层,当处理 tx:annotation-driven 元素时,Spring 会为您创建许多支持性的基础设施级别的 Bean 定义,而您无需直接关心它们,这是一件好事——它就是管用,确保任何标记了 @Transactional 的 Spring Bean 都被代理并带有一个 TransactionInterceptor。在 Spring 3.1 M1 之前,为了在 @Configuration 类中模拟此功能,您必须将这些基础设施 Bean 都声明为单独的 @Bean 方法。有些人确实走过这条路,我认为可以肯定地说这不是首选方法。为了避免这种额外的努力,Spring 3.0 提供了 @ImportResource 注解,允许 @Configuration 类在需要命名空间时有选择地“拉入”XML 配置:
/src/main/com/bank/config/code/TransferServiceConfig.java
@Configuration
@ImportResource("com/bank/config/xml/tx-config.xml")
public class TransferServiceConfig {
// ...
}
在上面的例子中,tx-config.xml 将包含 tx:annotation-driven/ 声明。这种混合模型能够工作固然很好,但再次强调,将配置分散在 XML 和代码之间并不理想。我们需要一种方法,能够将所有应用程序配置都在代码中指定。
到 TxAnnotationDriven要理解 FeatureSpecification 类,最好先思考 Spring 许多 XML 命名空间的本质。考虑以下内容:
这些元素中的每一个都指定了 Spring 容器某个功能的配置:组件扫描功能、注解驱动事务管理功能等。FeatureSpecification 类允许用户完全在代码中配置 Spring 容器的这些相同功能。
让我们看看 FeatureSpecification 的实际应用,以从 tx:annotation-driven/ 到 TxAnnotationDriven 的转变为例:
<beans>
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
变成:
/src/main/com/bank/config/code/TxFeature.java
@FeatureConfiguration
class TxFeature {
@Feature
public TxAnnotationDriven txAnnotationDriven(PlatformTransactionManager txManager) {
return new TxAnnotationDriven(txManager);
}
}
@FeatureConfiguration 类由 Spring 容器处理,并期望其包含返回 FeatureSpecifation 类型(如 TxAnnotationDriven)的 @Feature 方法。
@FeatureConfiguration 类被设计为 @Configuration 类的补充。在引导容器时,两者可以互换使用:
ApplicationContext ctx =
new AnnotationConfigApplicationContext(TransferServiceConfig.class, TxFeatures.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
并且一个可以 @Import 另一个:
@Configuration
@Import(TxFeature.class)
public class TransferServiceConfig { ... }
或者:
@FeatureConfiguration
@Import(TransferServiceConfig.class)
public class TxFeature { ... }
@FeatureConfiguration 类可以包含一个或多个 @Feature 方法。例如,我们可以在同一个类中配置组件扫描和注解驱动事务管理:
@FeatureConfiguration
class MyFeatures {
@Feature
public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
return new TxAnnotationDriven(txManager);
}
@Feature
public ComponentScanSpec scan() {
return new ComponentScanSpec("com.bank.service");
}
}
@Feature 方法在容器引导时被调用,返回的 FeatureSpecification 对象会被处理,以配置相关功能的实际内部细节。对于 TxAnnotationDriven,会配置代理创建,注册事务拦截器 Bean 等等;对于 ComponentScanSpec,会根据指定的值配置 ClassPathBeanDefinitionScanner,然后运行它以检测和注册标记了 @Component 或其他 Spring stereotype 注解的类。
@Feature 方法可以接受参数以获取 Spring Bean 的引用:
@Feature
public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
return new TxAnnotationDriven(txManager);
}
为了让容器提供参数,必须在其他地方(可能在 @Configuration 类中)声明一个类型为 PlatformTransactionManager 的 Spring Bean。这种“参数自动装配”的概念对于框架来说并不新鲜;例如,这类似于 Spring MVC 中 @InitBinder 和 @RequestMapping 方法的工作方式。引用实际 Spring Bean 实例的能力通过避免使用基于字符串的 Bean 名称来确保类型安全。
框架自带的 FeatureSpecification 实现设计得非常易于使用,特别是在编写 @Feature 方法时。正如您在下面的截图中看到的,ComponentScanSpec 的方法是可链式调用的,并且该类的公共 API 经过精心设计,以优化在 IDE 中工作时的内容辅助体验:
首个里程碑版本中已提供以下 FeatureSpecification 实现:
ComponentScanSpecTxAnnotationDrivenMvcAnnotationDrivenMvcResourcesMvcViewControllersMvcDefaultServletHandler您将在 Rossen 本系列即将发布的文章中看到许多 Mvc* 类型的使用,该文章将涵盖 Spring 3.1 M1 中对 Spring MVC 的增强。
第二个里程碑版本中,我们将为更多最常用的命名空间元素添加规范实现,例如 context:mbean-export/、aop:aspectj-autoproxy/ 等等。
请注意,并非所有 Spring XML 命名空间元素都相同。有些像上面提到的那些用于配置容器功能,而其他一些像 jee:jndi-lookup/ 和 util:*/ 命名空间中的所有内容,都只是方便的工具,用于在 XML 中完成在代码中无需任何特殊支持即可轻松完成的事情。因此,不要期望 Spring XML 元素与 FeatureSpecification 类型之间存在一对一的映射。只有那些有意义的元素才会被转换为代码。
正如用户可以定义自己的 Spring XML 命名空间并注册自己的 NamespaceHandler 和 BeanDefinitionParser 实现一样,整个 Feature* 模型自然也是完全开放的。更多详细信息请参考Javadoc,但足以说明您可以编写自己的 FeatureSpecification 类型并在 @Feature 方法中轻松使用它们。
对于那些已经实现了自定义命名空间和 BeanDefinitionParser 的用户,您会很高兴地知道,在框架内部,我们已经重构了现有的 BeanDefinitionParser,使其内部委托给 FeatureSpecifications。这极大地简化了解析器的实现,同时也确保了在 XML 和 @Configuration 风格之间重用关键的 Bean 注册逻辑。我们鼓励您在适当的地方考虑进行相同的重构。
尽管 Spring 在基于代码的配置方面不断前进,但必须再次强调,我们绝不会淘汰、废弃或以任何方式鼓励用户逐步淘汰使用 XML 配置容器。Spring XML 仍然很受欢迎,许多用户对此感到满意,特别是考虑到 SpringSource Tool Suite 为编写 Bean 配置文件提供的先进工具选项。@Configuration 和现在的 @FeatureConfiguration 类为那些更喜欢尽可能在 Java 中工作的用户提供了另一种一流的配置方法。因此请放心,Spring XML 不会消失——事实上,在 Spring 3.1 M1 中,我们通过添加嵌套的 元素、用于更方便构造函数注入的 c: 命名空间以及用于配置 Spring 新缓存功能的 cache: 命名空间,对 XML 给予了足够的关注。
我们希望您会发现 FeatureSpecification 类是现有 @Configuration 类模型自然而强大的扩展。请查看 accompanying this post 的示例应用程序,并务必提供反馈!