领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在我 之前关于 Spring 3.1 M1 的发布公告的帖子 中,我讨论了在使用 Spring <beans/> XML 配置容器时应用的新*bean 定义 profiles* 功能。今天我们将介绍新的 @Profile 注解,并了解如何在不使用 XML 而使用 @Configuration 类时应用此相同功能。在此过程中,我们将涵盖一些设计 @Configuration 类的最佳实践。
对于不熟悉 @Configuration 类的读者,您可以将其视为 Spring <beans/> XML 文件的纯 Java 等效项。我们之前已经在 博客 中 讨论过 这个特性集,并且参考文档 对此进行了很好的阐述。如果您需要介绍或复习,可以重新阅读这些资源。
正如我们将在本文及后续文章中看到的,Spring 3.1 中的 @Configuration 方法得到了极大的关注,目的是使其更加完善,并成为那些希望不使用 XML 配置应用程序的用户的首选方案。今天的文章将介绍其中一项增强功能:新的 @Profile 注解。
与上一篇文章一样,我准备了一个简短的示例,您可以按照示例进行操作并自己尝试。您可以在 https://github.com/cbeams/spring-3.1-profiles-java 找到它,所有设置细节都在 README 文件中。此示例包含上一篇文章中介绍的基于 XML 的配置,以及 @Configuration 类,分别位于 com.bank.config.xml 和 com.bank.config.code 包中。IntegrationTests JUnit 测试用例已针对每个包进行了复制;这应该有助于您比较和对比引导容器的两种样式。
@Configuration让我们开始吧!我们的任务很简单:将之前显示的基于 XML 的应用程序移植到 @Configuration 风格。我们在上一篇文章中从如下的 XML 配置开始:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<bean id="transferService" class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy" class="com.bank.service.internal.ZeroFeePolicy"/>
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
将其移植到 @Configuration 类非常直接
src/main/com/bank/config/code/TransferServiceConfig.java
@Configuration
public class TransferServiceConfig {
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource());
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
注意: EmbeddedDatabaseBuilder 是最初在 XML 中使用的 <jdbc:embedded-database/> 元素的底层组件。如您所见,它在 @Bean 方法中非常方便使用。
此时,我们的基于 @Configuration 的单元测试将通过(绿色条)
src/test/com/bank/config/code/IntegrationTests.java
public class IntegrationTests {
@Test
public void transferTenDollars() throws InsufficientFundsException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TransferServiceConfig.class);
ctx.refresh();
TransferService transferService = ctx.getBean(TransferService.class);
AccountRepository accountRepository = ctx.getBean(AccountRepository.class);
assertThat(accountRepository.findById("A123").getBalance(), equalTo(100.00));
assertThat(accountRepository.findById("C456").getBalance(), equalTo(0.00));
transferService.transfer(10.00, "A123", "C456");
assertThat(accountRepository.findById("A123").getBalance(), equalTo(90.00));
assertThat(accountRepository.findById("C456").getBalance(), equalTo(10.00));
}
}
上面使用了 AnnotationConfigApplicationContext,它允许直接注册 @Configuration 和其他 @Component 注解的类。这为配置容器提供了一种无字符串且类型安全的方式。没有 XML,这很好,但此时我们的应用程序存在与第一篇文章中相同的问题:当应用程序部署到生产环境时,独立的 DataSource 将不适用。需要从 JNDI 中查找。
这不成问题。让我们将嵌入式和 JNDI-based DataSource 分别提取到它们各自专用的 @Configuration 类中
src/main/com/bank/config/code/StandaloneDataConfig.java
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
src/main/com/bank/config/code/JndiDataConfig.java
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
此时,我们已在各自带有 @Profile 注解的 @Configuration 类中声明了两个不同的 DataSource bean。与 XML 一样,这些类以及其中的 @Bean 方法将根据当前活动的 Spring profile 被跳过或处理。但是,在我们看到这一点之前,我们首先需要完成重构。我们已经分离了两个可能的 DataSource bean,但在 TransferServiceConfig 中——特别是它的 accountRepository() 方法——如何引用它们呢?我们有几个选项,两者都始于理解 @Configuration 类是 @Autowired 注入的候选对象。这是因为,归根结底,@Configuration 对象在容器中被管理为“另一个 Spring bean”。我们来看一下
src/main/com/bank/config/code/TransferServiceConfig.java
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
通过使用上面的 @Autowired 注解,我们要求 Spring 容器为我们注入类型为 DataSource 的 bean,无论它在哪里声明——在 XML 中、在 @Configuration 类中,还是其他地方。然后在 accountRepository() 方法中,直接引用注入的 dataSource 字段。这是实现 @Configuration 类之间模块化的一种方式,并且在概念上与声明在不同 XML 文件中的两个 <bean> 元素之间的 ref 风格引用不无相似之处。
重构的最后一步是更新单元测试,不仅要引导 TransferServiceConfig,还要引导我们 DataSource bean 的 JNDI 和独立 @Configuration 变体
src/test/com/bank/config/code/IntegrationTests.java
public class IntegrationTests {
@Test
public void transferTenDollars() throws InsufficientFundsException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
// proceed with assertions as above ...
}
}
现在,我们所有的 @Configuration 类在引导时都可以被容器访问,并且根据活动的 profile(在本例中为“dev”),@Profile 注解的类及其 bean 将被处理或跳过。顺便提一下,您可以避免在上面列出每个 @Configuration 类,而是告诉 AnnotationConfigApplicationContext 直接扫描整个 .config 包,一次性检测到所有类。这相当于使用通配符(例如 **/*-config.xml)加载 Spring XML 文件。
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.scan("com.bank.config.code"); // find and register all @Configuration classes within
ctx.refresh();
无论您选择如何注册 @Configuration 类,此时我们的任务就完成了!我们将配置从 Spring <beans/> XML 移植到了 @Configuration 类,并使用 AnnotationConfigApplicationContext 直接从这些类引导了容器。
@Configuration 类结构我们的应用程序一切正常,JUnit 条也显示绿色,但仍有改进的空间。还记得 DataSource bean 是如何被 @Autowired 到 TransferServiceConfig 中的吗?这很有效,但并不清楚 bean *是从哪里*来的。如上所述,它可能来自 XML,也可能来自任何其他 @Configuration 类。我将在下面描述的技术引入了*面向对象配置*,并应有助于实现我们拥有自然 Java 配置的目标——一种可以充分利用 IDE 功能的配置。
如果我们考虑 StandaloneDataConfig 和 JndiDataConfig,它们实际上是同一种类的两种实现,因为它们都声明了一个具有以下签名的方**法**:
public DataSource dataSource();
似乎唯一缺少的是一个统一这两个类的接口。让我们引入一个——我们稍后会看到原因
src/main/com/bank/config/code/DataConfig.java
interface DataConfig {
DataSource dataSource();
}
当然,更新两个 @Configuration 类以实现这个新接口
@Configuration
public class StandaloneDataConfig implements DataConfig { ... }
@Configuration
public class JndiDataConfig implements DataConfig { ... }
这给我们带来了什么?就像我们将 DataSource bean 直接 @Autowired 到 TransferServiceConfig 中一样,*我们还可以注入 @Configuration 实例本身*。让我们看看实际效果
src/main/com/bank/config/code/TransferServiceConfig.java
@Configuration
public class TransferServiceConfig {
@Autowired DataConfig dataConfig;
// ...
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataConfig.dataSource());
}
// ...
}
这使我们能够通过 IDE 在代码库中进行完全导航。下面的截图显示了在调用 dataConfig.dataSource() 时按 CTRL-T 来获得“快速层次结构”悬停的效果
现在,我们可以很容易地提出“DataSource bean 是在哪里定义的?”这个问题,并且答案会限制在一组实现 DataConfig 的类型中。如果我们试图以一种对 Java 开发人员熟悉且有用的方式做事,这并不坏。
@Profile 的更高级用法值得快速提及的是,与许多 Spring 注解一样,@Profile 可以用作*元注解*。这意味着您可以定义自己的自定义注解,并用 @Profile 标记它们,Spring 仍然会检测到 @Profile 注解的存在,就好像它直接声明了一样。
package com.bank.annotation;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("dev")
pubilc @interface Dev {
}
这允许我们用新的自定义注解 @Dev 标记我们的 @Component 类,而不是强制使用 Spring 的 @Profile
@Dev @Component
public class MyDevService { ... }
或者,从上面的示例中,用 @Dev 标记我们的 StandaloneDataConfig 也可以
@Dev @Configuration
public class StandaloneDataConfig { ... }
Spring 3.1 的 bean 定义 profiles 功能在 XML 和 @Configuration 样式中都得到了完全支持。无论您偏好哪种样式,希望您会发现 profiles 有用。请继续提供反馈,因为它将直接影响即将发布的 3.1 M2。在下一篇文章中,我们将深入探讨 Spring 的新*Environment*抽象以及它如何帮助管理应用程序中的配置属性。敬请期待!