对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

环境抽象

Environment 接口 是容器中集成的一个抽象,用于建模应用程序环境的两个关键方面: 配置文件属性spring-doc.cadn.net.cn

配置文件是一个命名的、逻辑上的bean定义组,仅在给定的配置文件处于激活状态时才会被注册到容器中。无论在XML中定义还是使用注释定义,都可以将beans分配到配置文件中。与配置文件相关的Environment对象的作用是确定当前处于激活状态的配置文件(如果有),以及默认应处于激活状态的配置文件(如果有)。spring-doc.cadn.net.cn

属性在几乎所有应用程序中都起着重要作用,可能来自多种来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、临时的Properties对象、Map对象等。与属性相关的Environment对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。spring-doc.cadn.net.cn

Bean定义配置文件

Bean定义配置文件在核心容器中提供了一种机制,允许在不同的环境中注册不同的Bean。"环境"一词对不同的用户可能意味着不同的事情,此功能可以处理许多用例,包括:spring-doc.cadn.net.cn

考虑在实际应用中需要一个 DataSource 的第一个用例。在测试环境中,配置可能如下所示:spring-doc.cadn.net.cn

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

现在考虑如何将此应用程序部署到QA或生产环境,假设应用程序的数据源已注册到生产应用服务器的JNDI目录中。我们的dataSource bean现在如下列表所示:spring-doc.cadn.net.cn

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

问题是如何根据当前环境在两种变体之间进行切换。随着时间的推移,Spring 用户想出了多种方法来完成此操作,通常依赖于系统环境变量和包含<import/>语句的XML代码,其中包含${placeholder}标记,这些标记根据环境变量的值解析为正确的配置文件路径。Bean定义概要文件是核心容器功能,为这个问题提供了解决方案。spring-doc.cadn.net.cn

如果我们推广前面示例中展示的特定环境的bean定义用例,就会发现需要在某些上下文中注册某些bean定义,而在其他上下文中则不需要。你可以这样说,你希望在情况A中注册某一类bean定义,而在情况B中注册不同的类别。我们首先更新配置以反映这一需求。spring-doc.cadn.net.cn

使用 @Profile

@Profile 注解允许您指出当一个或多个指定的配置文件处于活动状态时,该组件适合进行注册。使用我们前面的例子,我们可以将 dataSource 配置重写如下:spring-doc.cadn.net.cn

@Configuration
@Profile("development")
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("development")
class StandaloneDataConfig {

	@Bean
	fun dataSource(): DataSource {
		return 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 = "") (1)
	public DataSource dataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}
1 @Bean(destroyMethod = "") 表示禁用默认销毁方法推断。
@Configuration
@Profile("production")
class JndiDataConfig {

	@Bean(destroyMethod = "") (1)
	fun dataSource(): DataSource {
		val ctx = InitialContext()
		return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
	}
}
1 @Bean(destroyMethod = "") 表示禁用默认销毁方法推断。
如前所述,使用 @Bean 方法时,通常选择通过使用 Spring 的 JndiTemplate/JndiLocatorDelegate 辅助类或者如前所示的直接 JNDI InitialContext 使用方式来进行编程式 JNDI 查找,而不是 JndiObjectFactoryBean 变体,因为后者会强制你将返回类型声明为 FactoryBean 类型。

配置文件字符串可能包含一个简单的配置文件名称(例如,production)或一个配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,production & us-east)。以下运算符在配置文件表达式中得到支持:spring-doc.cadn.net.cn

您不能在不使用括号的情况下将 &| 运算符混合使用。例如, production & us-east | eu-central 是一个无效的表达式。它必须表示为 production & (us-east | eu-central)

您可以使用 @Profile 作为 元注解,以实现创建自定义组合注解的目的。下面的示例定义了一个自定义的 @Production 注解,您可以将其用作 @Profile("production") 的替代品:spring-doc.cadn.net.cn

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果一个 @Configuration 类被标记为 @Profile,则该类的所有 @Bean 方法和 @Import 注解都会被跳过,除非指定了一个或多个活动的配置文件。如果一个 @Component@Configuration 类被标记为 @Profile({"p1", "p2"}),则在未激活配置文件 'p1' 或 'p2' 时,该类不会被注册或处理。如果给定的配置文件前缀带有 NOT 运算符(!),则只有在该配置文件未激活时才会注册带注解的元素。例如,给定 @Profile({"p1", "!p2"}),当配置文件 'p1' 激活或配置文件 'p2' 未激活时,将进行注册。

@Profile 也可以在方法级别声明,以仅包含配置类中的一个特定 bean(例如,某个特定 bean 的替代变体),如下例所示:spring-doc.cadn.net.cn

@Configuration
public class AppConfig {

	@Bean("dataSource")
	@Profile("development") (1)
	public DataSource standaloneDataSource() {
		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("dataSource")
	@Profile("production") (2)
	public DataSource jndiDataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}
1 standaloneDataSource 方法仅在 development 配置文件中可用。
2 jndiDataSource 方法仅在 production 配置文件中可用。
@Configuration
class AppConfig {

	@Bean("dataSource")
	@Profile("development") (1)
	fun standaloneDataSource(): DataSource {
		return EmbeddedDatabaseBuilder()
				.setType(EmbeddedDatabaseType.HSQL)
				.addScript("classpath:com/bank/config/sql/schema.sql")
				.addScript("classpath:com/bank/config/sql/test-data.sql")
				.build()
	}

	@Bean("dataSource")
	@Profile("production") (2)
	fun jndiDataSource() =
		InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 standaloneDataSource 方法仅在 development 配置文件中可用。
2 jndiDataSource 方法仅在 production 配置文件中可用。

@Profile@Bean 方法的情况下,可能会有特殊的情况:在相同 Java 方法名的重载 @Bean 方法的情况下(类似于构造函数重载),所有重载方法都需要一致地声明一个 @Profile 条件。如果条件不一致,只有重载方法中第一个声明的条件有效。因此,@Profile 不能用于根据特定参数签名选择一个重载方法而不是另一个。对于同一 bean 的所有工厂方法之间的选择,在创建时遵循 Spring 的构造函数解析算法。spring-doc.cadn.net.cn

如果您想定义具有不同配置文件条件的替代 bean,请使用不同的 Java 方法名称,这些名称通过 @Bean 名称属性指向相同的 bean 名称,如前面的示例所示。如果参数签名都相同(例如,所有变体都没有参数的工厂方法),那么这是在有效的 Java 类中表示这种安排的唯一方式(因为同一名称和参数签名的方法只能有一个)。spring-doc.cadn.net.cn

XML Bean 定义配置文件

XML对应的属性是profile元素的<beans>属性。我们前面的示例配置可以改写为两个XML文件,如下所示:spring-doc.cadn.net.cn

<beans profile="development"
	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/>元素, 如下面的示例所示:spring-doc.cadn.net.cn

<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="development">
		<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-doc.cadn.net.cn

XML 对应项不支持前面所述的配置文件表达式。但是,可以通过使用 ! 运算符来否定一个配置文件。还可以通过嵌套配置文件来应用逻辑“与”,如下例所示:spring-doc.cadn.net.cn

<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="production">
		<beans profile="us-east">
			<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
		</beans>
	</beans>
</beans>

在前面的示例中,如果同时激活了 dataSourceproduction 配置文件,则会公开 us-east Bean。spring-doc.cadn.net.cn

激活配置文件

在更新配置之后,我们仍然需要告诉Spring哪个配置文件是激活的。如果我们现在启动示例应用程序,将会看到一个NoSuchBeanDefinitionException被抛出,因为容器找不到名为dataSource的Spring Bean。spring-doc.cadn.net.cn

通过几种方式可以激活配置文件,但最直接的方法是针对 Environment API 进行编程,该 API 可通过 ApplicationContext 获得。下面的示例显示了如何操作:spring-doc.cadn.net.cn

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
	environment.setActiveProfiles("development")
	register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
	refresh()
}

此外,您还可以通过spring.profiles.active属性以声明方式激活配置文件,该属性可以通过系统环境变量、JVM系统属性、web.xml中的Servlet上下文参数,甚至作为JNDI中的条目来指定(参见PropertySource 抽象)。在集成测试中,可以通过在spring-test模块中使用@ActiveProfiles注解来声明活动的配置文件(参见使用环境配置文件进行上下文配置)。spring-doc.cadn.net.cn

请注意,配置文件并不是一个“非此即彼”的选择。您可以同时激活多个配置文件。编程方式上,您可以向setActiveProfiles()方法提供多个配置文件名称,该方法接受String…​个可变参数。下面的示例将激活多个配置文件:spring-doc.cadn.net.cn

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

以声明方式,spring.profiles.active 可能接受一个以逗号分隔的配置文件名称列表, 如下面的示例所示:spring-doc.cadn.net.cn

-Dspring.profiles.active="profile1,profile2"

默认配置文件

默认配置文件表示默认启用的配置文件。考虑以下示例:spring-doc.cadn.net.cn

@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();
	}
}
@Configuration
@Profile("default")
class DefaultDataConfig {

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

如果没有激活的配置文件,则会创建 dataSource。你可以将其视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何配置文件,则默认配置文件不适用。spring-doc.cadn.net.cn

默认配置文件的名称是 default。您可以通过在 Environment 上使用 setDefaultProfiles(),或通过声明式方式使用 spring.profiles.default 属性来更改默认配置文件的名称。spring-doc.cadn.net.cn

PropertySource 抽象

Spring 的 Environment 抽象提供了对可配置属性源层次结构的搜索操作。请参阅以下列表:spring-doc.cadn.net.cn

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

在前面的代码片段中,我们看到一种高层次的方法,用于询问Spring当前环境是否定义了my-property属性。为了解决这个问题,Environment对象会在一组PropertySource 对象上进行搜索。一个PropertySource是对任何键值对源的简单抽象,Spring的StandardEnvironment 配置了两个PropertySource对象——一个表示JVM系统属性集合(System.getProperties()),另一个表示系统环境变量集合(System.getenv())。spring-doc.cadn.net.cn

这些默认属性源适用于 StandardEnvironment,用于独立应用程序。 StandardServletEnvironment 会包含额外的默认属性源,包括 servlet 配置、servlet 上下文参数,以及如果 JNDI 可用的话,还包括一个 JndiPropertySource

具体来说,当您使用 StandardEnvironment 时,如果在运行时存在 my-property 系统属性或 my-property 环境变量,调用 env.containsProperty("my-property") 将返回 true。spring-doc.cadn.net.cn

搜索是分层进行的。默认情况下,系统属性优先于环境变量。因此,如果在调用 env.getProperty("my-property")my-property 属性在两个位置都已设置,系统属性值“获胜”并被返回。 请注意,属性值不会合并,而是被前面的条目完全覆盖。spring-doc.cadn.net.cn

对于常见的 StandardServletEnvironment,完整的优先级层次如下,优先级最高的条目在顶部:spring-doc.cadn.net.cn

  1. ServletConfig 参数(如适用 — 例如,在 DispatcherServlet 上下文中)spring-doc.cadn.net.cn

  2. Servlet上下文参数(web.xml中的context-param条目)spring-doc.cadn.net.cn

  3. JNDI 环境变量(java:comp/env/ 个条目)spring-doc.cadn.net.cn

  4. JVM 系统属性(-D 命令行参数)spring-doc.cadn.net.cn

  5. JVM 系统环境(操作系统环境变量)spring-doc.cadn.net.cn

最重要的是,整个机制是可配置的。也许你有一个自定义的属性来源,想要将其集成到此搜索中。为此,实现并实例化你自己的 PropertySource,并将其添加到当前 EnvironmentPropertySources 集合中。下面的示例展示了如何操作:spring-doc.cadn.net.cn

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

在前面的代码中,MyPropertySource 在搜索中具有最高优先级。如果它包含一个 my-property 属性,则会检测并返回该属性,优于任何其他 my-property 属性在任何其他 PropertySource 中的属性。 MutablePropertySources API 提供了许多方法,允许对属性源集进行精确操作。spring-doc.cadn.net.cn

使用 @PropertySource

@PropertySource 注解为向 Spring 的 Environment 添加 PropertySource 提供了便捷且声明式的方法。spring-doc.cadn.net.cn

给定一个名为 app.properties 的文件,其中包含键值对 testbean.name=myTestBean, 以下 @Configuration 类以这样的方式使用 @PropertySource, 使得对 testBean.getName() 的调用返回 myTestBeanspring-doc.cadn.net.cn

@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;
 }
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {

	@Autowired
	private lateinit var env: Environment

	@Bean
	fun testBean() = TestBean().apply {
		name = env.getProperty("testbean.name")!!
	}
}

任何在 @PropertySource 资源位置中出现的 ${…​} 占位符都会根据已注册到环境中的属性源进行解析,如下例所示:spring-doc.cadn.net.cn

@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;
 }
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {

	@Autowired
	private lateinit var env: Environment

	@Bean
	fun testBean() = TestBean().apply {
		name = env.getProperty("testbean.name")!!
	}
}

假设已注册的属性源之一(例如系统属性或环境变量)中已经包含my.placeholder,则占位符将解析为相应的值。如果没有,则使用default/path作为默认值。如果没有指定默认值且无法解析属性,则会抛出IllegalArgumentExceptionspring-doc.cadn.net.cn

@PropertySource 注解是可重复的,根据 Java 8 的约定。 但是,所有这些 @PropertySource 注解都需要在同一级别声明, 要么直接在配置类上,要么作为同一自定义注解中的元注解。 直接注解和元注解混合使用是不推荐的,因为直接注解会覆盖元注解。

语句中的占位符解析

从历史上看,元素中的占位符值只能根据JVM系统属性或环境变量进行解析。这种情况现在已经改变。由于Environment抽象在整个容器中得到集成,通过它来路由占位符的解析变得很容易。这意味着您可以按照自己喜欢的任何方式配置解析过程。您可以更改通过系统属性和环境变量搜索的优先级,或者完全删除它们。您还可以根据需要添加自己的属性源。spring-doc.cadn.net.cn

具体来说,以下语句无论customer属性在何处定义都有效,只要它在Environment中可用:spring-doc.cadn.net.cn

<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
</beans>