Spring | 3.13 环境抽象

3.13 环境抽象

环境是集成在容器中的抽象,它对应用程序环境的两个关键方面建模:概要文件和属性。
概要文件是一个命名的bean定义逻辑组,只有在给定概要文件处于活动状态时才向容器注册。可以将bean分配给一个概要文件,无论是在XML中定义的还是通过注解定义的。与概要文件相关的环境对象的角色是确定哪些概要文件(如果有的话)当前是活动的,以及哪些概要文件(如果有的话)在默认情况下应该是活动的。
属性在几乎所有的应用程序中都扮演着重要的角色,并且可能来自各种各样的源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特殊属性对象、映射等等。环境对象与属性之间的关系的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。

3.13.1 Bean定义概要文件

Bean定义概要文件是核心容器中的一种机制,它允许在不同的环境中注册不同的Bean。环境这个词对不同的用户可能意味着不同的东西,这个功能可以帮助许多用例,包括:

  • 在开发中处理内存中的数据源,而在QA或生产中从JNDI查找相同的数据源
  • 只有在将应用程序部署到性能环境中时才注册监视基础设施
  • 为客户A和客户B的部署注册bean的定制实现

让我们考虑实际应用程序中需要数据源的第一个用例。在测试环境中,配置可能如下所示:

@Bean
public DataSource dataSource() {
	return new EmbeddedDatabaseBuilder()
		.setType(EmbeddedDatabaseType.HSQL)
		.addScript("my-schema.sql")
		.addScript("my-test-data.sql")
		.build();
}

现在让我们考虑如何将这个应用程序部署到QA或生产环境中,假设应用程序的数据源将注册到生产应用程序服务器的JNDI目录中。我们的数据源bean现在看起来是这样的:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
	Context ctx = new InitialContext();
	return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的推移,Spring用户已经设计了许多方法来实现这一点,通常依赖于系统环境变量和包含${placeholder}令牌的XML < import/>语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义概要文件是一个核心容器特性,它为这个问题提供了一个解决方案。
如果我们概括上面特定于环境的bean定义的示例用例,我们最终需要在特定上下文中注册特定的bean定义,而不是在其他上下文中注册。你可以说,你想要在情景a中注册bean定义的某个概要文件,在情景b中注册另一个概要文件。

@Profile

@Profile注解允许你在一个或多个指定概要文件处于活动状态时指出组件有资格注册。使用上面的例子,我们可以重写数据源配置如下:

@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();
	}
}
@Configuration
@Profile("production")
public class JndiDataConfig {

	@Bean(destroyMethod="")
	public DataSource dataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}

注意:如前所述,对于@Bean方法,你通常会选择使用编程JNDI查找:要么使用Spring的JndiTemplate/JndiLocatorDelegate helper,要么使用上面显示的直接JNDI InitialContext,但是不使用JndiObjectFactoryBean变体,因为它将强制你将返回类型声明为FactoryBean类型。

@Profile可以用作元注解,用于创建自定义组合注解。下面的例子定义了一个自定义的@Production注解,它可以作为@Profile(“production”)的下拉式替换:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

@Profile也可以在方法层声明,只包含配置类的一个特定bean:

@Configuration
public class AppConfig {

	@Bean
	@Profile("dev")
	public DataSource devDataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.addScript("classpath:com/bank/config/sql/test-data.sql")
			.build();
	}

	@Bean
	@Profile("production")
	public DataSource productionDataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}

注意:如果@Configuration类被标记为@Profile,那么与该类关联的所有@Bean方法和@Import注解都将被绕过,除非一个或多个指定的概要文件处于活动状态。如果@Component或@Configuration类被标记为@Profile({“p1”, “p2”}),那么这个类将不会被注册/处理,除非配置文件’p1’和/或’p2’已经被激活。如果给定概要文件的前缀是NOT操作符(!),那么如果概要文件不是活动的,则会注册带注解的元素。例如,给定@Profile({“p1”, “!p2”}),如果配置文件’p1’是活动的,或者配置文件’p2’不是活动的,就会发生注册。

3.13.2 XML bean定义概要文件

XML对应项是< beans>元素的profile属性。我们上面的示例配置可以用两个XML文件重写如下:

<beans profile="dev"
	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="...">

	<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>

<beans profile="production"
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="...">

	<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

也可以避免在同一个文件中分割和嵌套< beans/>元素:

<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"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="...">

	<!-- other bean definitions -->

	<beans profile="dev">
		<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>

	<beans profile="production">
		<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
	</beans>
</beans>

spring-bean.xsd受到限制,只允许将这些元素作为文件中的最后一个元素。这应该有助于提供灵活性,而不会在XML文件中引起混乱。

激活一个概要文件

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件是活动的。如果我们现在启动示例应用程序,我们将看到抛出NoSuchBeanDefinitionException,因为容器无法找到名为dataSource的Spring bean。
激活一个概要文件可以通过几种方式来实现,但最直接的方式是通过编程来实现,而环境API可以通过ApplicationContext来实现:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

另外,配置文件也可以通过spring.profiles.active声明性地激活,可以通过web.xml中的系统环境变量、JVM系统属性、servlet上下文参数来指定。或者甚至作为JNDI中的一个条目(参见3.13.3节,“PropertySource抽象”)。在集成测试中,可以通过spring-test模块中的@ActiveProfiles注解声明活动概要文件(请参阅“使用环境概要文件进行上下文配置”一节)。
请注意,配置文件不是一个“非此即彼”的命题;可以同时激活多个概要文件。通过编程,只需向setActiveProfiles()方法提供多个配置文件名称,该方法接受String…参数:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

spring.profiles.active可以接受逗号分隔的概要名称列表:

 Dspring.profiles.active="profile1,profile2"
默认的概要配置

默认配置文件表示默认启用的配置文件。考虑以下:

@Configuration
@Profile("default")
public class DefaultDataConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.build();
	}
}

如果没有激活配置文件,将创建上面的数据源;这可以看作是为一个或多个bean提供默认定义的一种方法。如果启用了任何配置文件,则不应用默认配置文件。
可以使用环境上的setDefaultProfiles()或使用spring.profiles.default属性声明性地更改默认概要文件的名称。

3.13.3 PropertySource抽象

Spring的环境抽象在可配置的属性源层次结构上提供搜索操作。要充分解释,请考虑以下各点:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,我们看到了询问Spring是否为当前环境定义了foo属性的高级方法。为了回答这个问题,Environment对象对一组PropertySource对象执行搜索。PropertySource是对任何键值对源的简单抽象,Spring的StandarEnvironment配置了两个PropertySource对象——一个表示JVM系统属性集(一个是System.getproperties()),另一个表示系统环境变量集(一个是System .getenv())。

注意:这些默认属性源用于StandardEnvironment,用于独立应用程序。StandardServletEnvironment包含了额外的默认属性源,包括servlet配置和servlet上下文参数。它可以选择性地启用JndiPropertySource。有关详细信息,请参阅javadocs。

具体来说,当使用StandardEnvironment时,如果foo系统属性或foo环境变量在运行时出现,对env.containsProperty(“foo”)的调用将返回true。

注意:执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此,如果在调用env.getProperty(“foo”)期间,foo属性恰好在这两个地方都被设置,那么系统属性值将“win”,并优先于环境变量返回。注意,属性值不会被合并,而是被前面的条目完全覆盖。
注意:对于公共StandardServletEnvironment,完整的层次结构如下所示,最高优先级的条目位于顶部:
ServletConfig参数(如果适用,例如在DispatcherServlet上下文中)
ServletContext参数(web.xml context-param条目)
JNDI环境变量(“java:comp/env/”条目)
JVM系统属性(“-D”命令行参数)
JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许你有一个想要集成到此搜索中的自定义属性源。没问题——只需实现并实例化你自己的PropertySource,然后将它添加到当前环境的PropertySource集合中:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码中,MyPropertySource以搜索中的最高优先级添加。如果它包含一个foo属性,它将被检测到并在任何其他PropertySource中的任何foo属性之前返回。MutablePropertySources API公开了许多方法,这些方法允许精确地操作一组属性源。

3.13.4 @PropertySource

@PropertySource注解为向Spring的环境添加PropertySource提供了一种方便的声明性机制。
给定一个包含键/值对testbean.name=myTestBean的文件“app.properties”,下面的@Configuration类使用@PropertySource,这样对testBean.getName()的调用将返回“myTestBean”。

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

@PropertySource资源位置中出现的任何${…}占位符都将根据已经在环境中注册的一组属性源进行解析。例如:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
 @Autowired
 Environment env;

 @Bean
 public TestBean testBean() {
  TestBean testBean = new TestBean();
  testBean.setName(env.getProperty("testbean.name"));
  return testBean;
 }
}

假设“my.placeholder”存在于已注册的属性源之一中,例如系统属性或环境变量,占位符将被解析为相应的值。如果没有,则使用“default/path”作为默认值。如果没有指定默认值,并且无法解析属性,则会抛出IllegalArgumentException。

3.13.5 语句中的占位符解析

从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。情况不再是这样了。因为环境抽象是在整个容器中集成的,所以很容易通过它路由占位符的解析。这意味着你可以以任何你喜欢的方式配置解析过程:更改搜索系统属性和环境变量的优先级,或者完全删除它们;根据需要将你自己的属性源添加到混合中。
具体来说,下面的语句不管在哪里定义客户属性,只要它在环境中可用,都是有效的:

<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值