先行一步
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 中的说明进行操作。
到 @Bean
Spring 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 实现:
ComponentScanSpec
TxAnnotationDriven
MvcAnnotationDriven
MvcResources
MvcViewControllers
MvcDefaultServletHandler
您将在 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 的示例应用程序,并务必提供反馈!