Spring 3.1 M1:引入@Profile

工程 | Chris Beams | 2011年2月14日 | ...

介绍

在我之前发布的文章中,我讨论了在使用Spring <beans/> XML配置容器时应用的新bean定义配置文件功能。今天我们将介绍新的@Profile注解,并了解如何使用@Configuration类而不是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测试用例;这应该可以帮助您比较和对比这两种容器引导方式。

从XML到@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<jdbc:embedded-database/>元素(最初在XML中使用)的基础组件。如您所见,它非常方便在@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,这很好,但是此时我们的应用程序存在与我们在第一篇文章中看到的相同问题:当应用程序部署到生产环境中时,独立数据源将毫无意义。它需要从JNDI查找。

这不是问题。让我们将基于嵌入式和JNDI的数据源分解到它们各自的专用@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配置文件跳过或处理。但是,在我们看到它起作用之前,我们首先需要完成重构。我们已经拆分了两个可能的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类在引导时都可用于容器,并且根据活动配置文件(在本例中为“dev”),将处理或跳过@Profile注解的类及其bean。快速说明一下,您可以避免列出每个@Configuration类,而是告诉AnnotationConfigApplicationContext只需扫描整个.config包,即可一举检测到我们所有的类。这与基于通配符加载Spring XML文件(例如,**/*-config.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@AutowiredTransferServiceConfig中?这很好用,但并不十分清楚bean来自哪里。如上所述,它可能来自XML,也可能来自任何其他@Configuration类。我将在下面描述的技术引入了面向对象的配置,应该进一步实现我们的目标,即拥有基于Java的自然配置——一种可以充分利用IDE功能的配置。

如果我们考虑StandaloneDataConfigJndiDataConfig,它们实际上是同一种类的两个类,因为它们都声明了一个具有以下签名的方法


		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以获取“快速层次结构”悬停的结果

Quick implementation hierarchy for DataConfig.dataSource()

现在很容易问“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定义配置文件功能在XML和@Configuration样式中都得到完全支持。无论您喜欢哪种样式,我们都希望您能发现配置文件有用。请继续提供反馈,因为它将直接影响即将发布的3.1 M2。在下一篇文章中,我们将深入探讨Spring新的Environment抽象以及它如何在管理应用程序中的配置属性方面提供帮助。敬请关注!

获取Spring通讯

与Spring通讯保持联系

订阅

领先一步

VMware提供培训和认证,以加快您的进度。

了解更多

获取支持

Tanzu Spring在一个简单的订阅中提供对OpenJDK™、Spring和Apache Tomcat®的支持和二进制文件。

了解更多

即将举行的活动

查看Spring社区中所有即将举行的活动。

查看全部