Environment Abstraction

Environment 接口是集成在容器中的一个抽象,它对应用程序环境的两个关键方面进行建模: profilesproperties

The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.

配置文件是一个具名的逻辑 bean 定义组,仅在给定的配置文件处于活动状态时才向容器注册。Bean 可以分配给配置文件,无论是否在 XML 中定义或使用注解定义。Environment 对象与配置文件之间的关系在于确定当前哪些配置文件(如果有)处于活动状态,以及哪些配置文件(如果有)应默认处于活动状态。

A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.

属性在几乎所有应用程序中都起着重要作用,并且可能源自各种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、Servlet 上下文参数、特别 Properties 对象、Map 对象等等。与属性相关的 Environment 对象的作用是为用户提供一个便捷的服务接口,用于配置属性源并从中解析属性。

Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on. The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

Bean Definition Profiles

Bean 定义配置文件在核心容器中提供了一种机制,该机制允许在不同的环境中注册不同的 bean。不同用户对“环境”一词有不同的理解,而此功能可以帮助解决许多用例,包括:

Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments. The word, “environment,” can mean different things to different users, and this feature can help with many use cases, including:

  • Working against an in-memory datasource in development versus looking up that same datasource from JNDI when in QA or production.

  • Registering monitoring infrastructure only when deploying an application into a performance environment.

  • Registering customized implementations of beans for customer A versus customer B deployments.

在需要 DataSource 的实际应用程序中考虑第一个用例。在测试环境中,配置可能如下:

Consider the first use case in a practical application that requires a DataSource. In a test environment, the configuration might resemble the following:

  • Java

  • Kotlin

@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 现在看起来如下:

Now consider how this application can be deployed into a QA or production environment, assuming that the datasource for the application is registered with the production application server’s JNDI directory. Our dataSource bean now looks like the following listing:

  • Java

  • Kotlin

@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 用户已经设计了许多实现此操作的方法,通常依赖于系统环境变量和 XML <import/> 语句的组合,其中包含根据环境变量的值解析为正确配置文件路径的 ${placeholder} 令牌。Bean 定义配置文件是一项核心容器功能,它提供了解决此问题的方法。

The problem is how to switch between using these two variations based on the current environment. Over time, Spring users have devised a number of ways to get this done, usually relying on a combination of system environment variables and XML <import/> statements containing ${placeholder} tokens that resolve to the correct configuration file path depending on the value of an environment variable. Bean definition profiles is a core container feature that provides a solution to this problem.

如果我们对前一个示例中显示的环境特定 bean 定义用例进行概括,最终会得出需要在特定上下文中注册某些 bean 定义但在其他上下文中不需要注册某些 bean 定义。您可以说您希望在情况 A 中注册某个 bean 定义配置文件,在情况 B 中注册另一个配置文件。我们首先更新我们的配置以反映此需求。

If we generalize the use case shown in the preceding example of environment-specific bean definitions, we end up with the need to register certain bean definitions in certain contexts but not in others. You could say that you want to register a certain profile of bean definitions in situation A and a different profile in situation B. We start by updating our configuration to reflect this need.

Using @Profile

@Profile 注释可让您指明某个组件有资格进行注册,前提是一个或多个指定的情境处于活动状态。使用我们的前面示例,我们可将 `dataSource`配置重写如下:

The @Profile annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the dataSource configuration as follows:

  • Java

  • Kotlin

@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()
	}
}
Java
@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 = "") disables default destroy method inference.
Kotlin
@Configuration
@Profile("production")
class JndiDataConfig {

	@Bean(destroyMethod = "") (1)
	fun dataSource(): DataSource {
		val ctx = InitialContext()
		return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
	}
}
2 @Bean(destroyMethod = "") disables default destroy method inference.

如前文所述,对于 @{48} 方法,你通常选择使用Spring的@{49}/@{50}帮助程序或前面演示了直接JNDI @{51}使用法,通过编程的方式JNDI 查找,但不是@{52}变量,它会强制你将返回类型声明为@{53}类型。

As mentioned earlier, with @Bean methods, you typically choose to use programmatic JNDI lookups, by using either Spring’s JndiTemplate/JndiLocatorDelegate helpers or the straight JNDI InitialContext usage shown earlier but not the JndiObjectFactoryBean variant, which would force you to declare the return type as the FactoryBean type.

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

The profile string may contain a simple profile name (for example, production) or a profile expression. A profile expression allows for more complicated profile logic to be expressed (for example, production & us-east). The following operators are supported in profile expressions:

  • !: A logical NOT of the profile

  • &: A logical AND of the profiles

  • |: A logical OR of the profiles

你不能混合使用@{54}和@{55}运算符而不使用括号。例如,@{56}不是一个有效的表达式。它必须表示为@{57}。

You cannot mix the & and | operators without using parentheses. For example, production & us-east | eu-central is not a valid expression. It must be expressed as production & (us-east | eu-central).

你可以使用 @Profile 作为 meta-annotation 以便创建一个自定义组合注释。以下示例定义了一个自定义 @Production 注释,你可以使用它作为 @Profile("production") 的直接替代:

You can use @Profile as a meta-annotation for the purpose of creating a custom composed annotation. The following example defines a custom @Production annotation that you can use as a drop-in replacement for @Profile("production"):

  • Java

  • Kotlin

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production

如果一个@{58}类用@{59}标记,则除非激活了一个或多个指定的配置文件,否则将绕过与该类关联的所有@{60}方法和@{61}注释。如果一个@{62}或@{63}类用@{64}标记,则除非激活了配置文件“p1”或“p2”,否则不注册或处理该类。如果给定的配置文件添加了NOT运算符前缀(@{65}),则仅当该配置文件未激活时才注册注释的元素。例如,给定@{66},如果配置文件“p1”处于活动状态或配置文件“p2”处于非活动状态,则会进行注册。

If a @Configuration class is marked with @Profile, all of the @Bean methods and @Import annotations associated with that class are bypassed unless one or more of the specified profiles are active. If a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class is not registered or processed unless profiles 'p1' or 'p2' have been activated. If a given profile is prefixed with the NOT operator (!), the annotated element is registered only if the profile is not active. For example, given @Profile({"p1", "!p2"}), registration will occur if profile 'p1' is active or if profile 'p2' is not active.

@Profile 也可以在方法级别声明,以便仅包含一个配置类的特定 Bean(例如,对于特定 Bean 的备用变体),如下例所示:

@Profile can also be declared at the method level to include only one particular bean of a configuration class (for example, for alternative variants of a particular bean), as the following example shows:

Java
@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 The standaloneDataSource method is available only in the development profile.
2 The jndiDataSource method is available only in the production profile.
Kotlin
@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
}
3 The standaloneDataSource method is available only in the development profile.
4 The jndiDataSource method is available only in the production profile.

使用 @Bean 方法上的 @Profile,可能会应用一个特殊情景:对于同名 Java 方法的重载 @Bean 方法(类似于构造函数重载),需要在所有重载方法上一致地声明 @Profile 条件。如果条件不一致,则只有重载方法中第一个声明的条件才有意义。因此,@Profile 不能用于使用特定参数签名选择一个重载方法而不是另一个重载方法。在创建时,针对同一 Bean 的所有工厂方法之间的解析遵循 Spring 的构造函数解析算法。

With @Profile on @Bean methods, a special scenario may apply: In the case of overloaded @Bean methods of the same Java method name (analogous to constructor overloading), a @Profile condition needs to be consistently declared on all overloaded methods. If the conditions are inconsistent, only the condition on the first declaration among the overloaded methods matters. Therefore, @Profile can not be used to select an overloaded method with a particular argument signature over another. Resolution between all factory methods for the same bean follows Spring’s constructor resolution algorithm at creation time.

如果你想使用不同的配置文件条件定义备用 Bean,请使用 @Bean name 属性指向同一 Bean 名称的不同 Java 方法名,如前面的示例所示。如果所有参数签名都相同(例如,所有变体都没有参数工厂方法),这首先是表示这种安排在有效 Java 类中的唯一方法(因为对于一个特定名称和参数签名只能有一个方法)。

If you want to define alternative beans with different profile conditions, use distinct Java method names that point to the same bean name by using the @Bean name attribute, as shown in the preceding example. If the argument signatures are all the same (for example, all of the variants have no-arg factory methods), this is the only way to represent such an arrangement in a valid Java class in the first place (since there can only be one method of a particular name and argument signature).

XML Bean Definition Profiles

XML 等效项是 <beans> 元素的 profile 属性。我们可以使用以下两种 XML 文件重新编写前面示例中的配置:

The XML counterpart is the profile attribute of the <beans> element. Our preceding sample configuration can be rewritten in two XML files, as follows:

<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/> 元素,如下例所示:

It is also possible to avoid that split and nest <beans/> elements within the same file, as the following example shows:

<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 文件混乱。

The spring-bean.xsd has been constrained to allow such elements only as the last ones in the file. This should help provide flexibility without incurring clutter in the XML files.

XML 等效项不支持前面描述的配置文件表达式。但是,可以通过使用 ! 运算符来否定配置文件。还可以通过嵌套配置文件来应用逻辑“and”,如下例所示:

The XML counterpart does not support the profile expressions described earlier. It is possible, however, to negate a profile by using the ! operator. It is also possible to apply a logical “and” by nesting the profiles, as the following example shows:

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

在前面的示例中,如果 productionus-east 配置文件都处于活动状态,则公开 dataSource Bean。

In the preceding example, the dataSource bean is exposed if both the production and us-east profiles are active.

Activating a Profile

现在我们已经更新了配置,我们仍然需要指示 Spring 哪个配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到抛出一个 NoSuchBeanDefinitionException,因为容器找不到名为 dataSource 的 Spring Bean。

Now that we have updated our configuration, we still need to instruct Spring which profile is active. If we started our sample application right now, we would see a NoSuchBeanDefinitionException thrown, because the container could not find the Spring bean named dataSource.

可以通过多种方法激活配置文件,但最直接的方法是针对 ApplicationContext 提供的 Environment API 以编程方式进行激活。以下示例展示了如何进行操作:

Activating a profile can be done in several ways, but the most straightforward is to do it programmatically against the Environment API which is available through an ApplicationContext. The following example shows how to do so:

  • Java

  • Kotlin

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 Abstraction)。在集成测试中,可以使用 spring-test 模块(见 context configuration with environment profiles)中的 @ActiveProfiles 注释来声明活动概要文件。

In addition, you can also declaratively activate profiles through the spring.profiles.active property, which may be specified through system environment variables, JVM system properties, servlet context parameters in web.xml, or even as an entry in JNDI (see PropertySource Abstraction). In integration tests, active profiles can be declared by using the @ActiveProfiles annotation in the spring-test module (see context configuration with environment profiles ).

请注意,配置文件不是一个“要么-要么”命题。你可以同时激活多个配置文件。以编程方式,你可以向接受 String…​ vararg 的 setActiveProfiles() 方法提供多个配置文件名称。以下示例激活多个配置文件:

Note that profiles are not an “either-or” proposition. You can activate multiple profiles at once. Programmatically, you can provide multiple profile names to the setActiveProfiles() method, which accepts String…​ varargs. The following example activates multiple profiles:

  • Java

  • Kotlin

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

通过声明方式,spring.profiles.active 可以接受一个逗号分隔的配置文件名称列表,如下例所示:

Declaratively, spring.profiles.active may accept a comma-separated list of profile names, as the following example shows:

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

Default Profile

默认配置文件表示在没有配置文件处于活动状态时启用的配置文件。考虑以下示例:

The default profile represents the profile that is enabled if no profile is active. Consider the following example:

  • Java

  • Kotlin

@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()
	}
}

如果 [没有处于活动状态的配置文件,beans-definition-profiles-enable],则创建 dataSource。你可以将其视为提供一个或多个 Bean 的默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。

If beans-definition-profiles-enable, the dataSource is created. You can see this as a way to provide a default definition for one or more beans. If any profile is enabled, the default profile does not apply.

默认配置文件的名称是 default。你可以通过对 Environment 使用 setDefaultProfiles() 或通过声明方式使用 spring.profiles.default 属性来更改默认配置文件的名称。

The name of the default profile is default. You can change the name of the default profile by using setDefaultProfiles() on the Environment or, declaratively, by using the spring.profiles.default property.

PropertySource Abstraction

Spring 的 Environment 抽象提供了可配置属性源层次结构的搜索操作。考虑以下列表:

Spring’s Environment abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing:

  • Java

  • Kotlin

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())。

In the preceding snippet, we see a high-level way of asking Spring whether the my-property property is defined for the current environment. To answer this question, the Environment object performs a search over a set of PropertySource objects. A PropertySource is a simple abstraction over any source of key-value pairs, and Spring’s StandardEnvironment is configured with two PropertySource objects — one representing the set of JVM system properties (System.getProperties()) and one representing the set of system environment variables (System.getenv()).

这些默认属性源适用于 StandardEnvironment,用于独立应用程序。https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/support/StandardServletEnvironment.html[StandardServletEnvironment] 会填充其他默认属性源,包括 servlet 配置、servlet 上下文参数以及(如果 JNDI 可用的话)一个 JndiPropertySource

These default property sources are present for StandardEnvironment, for use in standalone applications. StandardServletEnvironment is populated with additional default property sources including servlet config, servlet context parameters, and a JndiPropertySource if JNDI is available.

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

Concretely, when you use the StandardEnvironment, the call to env.containsProperty("my-property") returns true if a my-property system property or my-property environment variable is present at runtime.

执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用 env.getProperty("my-property") 期间 my-property 属性恰好在这两个地方设置,则系统属性值“获胜”并被返回。请注意,属性值不会合并,而是被前面的条目完全覆盖。

The search performed is hierarchical. By default, system properties have precedence over environment variables. So, if the my-property property happens to be set in both places during a call to env.getProperty("my-property"), the system property value “wins” and is returned. Note that property values are not merged but rather completely overridden by a preceding entry.

对于常见的 StandardServletEnvironment,完整的层次结构如下所示,优先级最高条目位于顶部:

For a common StandardServletEnvironment, the full hierarchy is as follows, with the highest-precedence entries at the top:

  1. ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)

  2. ServletContext parameters (web.xml context-param entries)

  3. JNDI environment variables (java:comp/env/ entries)

  4. JVM system properties (-D command-line arguments)

  5. JVM system environment (operating system environment variables)

最重要的是,整个机制是可配置的。也许您有一个自定义属性源,希望将其集成到此搜索中。为此,请实现并实例化您自己的 PropertySource,并将其添加到当前 EnvironmentPropertySources 集合中。以下示例展示了如何执行此操作:

Most importantly, the entire mechanism is configurable. Perhaps you have a custom source of properties that you want to integrate into this search. To do so, implement and instantiate your own PropertySource and add it to the set of PropertySources for the current Environment. The following example shows how to do so:

  • Java

  • Kotlin

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`属性,则会检测到并返回该属性,而不是任何其他 `PropertySource`中的任何 `my-property`属性。https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/MutablePropertySources.html[`MutablePropertySources] API 公开了许多方法,允许对属性源的集合进行精确控制。

In the preceding code, MyPropertySource has been added with highest precedence in the search. If it contains a my-property property, the property is detected and returned, in favor of any my-property property in any other PropertySource. The MutablePropertySources API exposes a number of methods that allow for precise manipulation of the set of property sources.

Using @PropertySource

@PropertySource 注释提供了一种便利且声明性的机制,用于向 Spring 的 Environment`添加 `PropertySource

The @PropertySource annotation provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment.

假设有一个名为 app.properties 的文件,其中包含键值对 testbean.name=myTestBean,以下 @Configuration 类以这样一种方式使用 @PropertySource,即对 testBean.getName() 的调用返回 myTestBean

Given a file called app.properties that contains the key-value pair testbean.name=myTestBean, the following @Configuration class uses @PropertySource in such a way that a call to testBean.getName() returns myTestBean:

  • Java

  • Kotlin

@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 资源位置中的任何 ${…​} 占位符都将根据已针对环境注册的属性源集合进行解析,如下例所示:

Any ${…​} placeholders present in a @PropertySource resource location are resolved against the set of property sources already registered against the environment, as the following example shows:

  • Java

  • Kotlin

@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 被用作默认值。如果未指定默认值且无法解析属性,则会抛出 IllegalArgumentException

Assuming that my.placeholder is present in one of the property sources already registered (for example, system properties or environment variables), the placeholder is resolved to the corresponding value. If not, then default/path is used as a default. If no default is specified and a property cannot be resolved, an IllegalArgumentException is thrown.

@PropertySource 可用作可重复注解。@PropertySource 还可以用作元注解来使用属性覆盖创建自定义复合注解。

@PropertySource can be used as a repeatable annotation. @PropertySource may also be used as a meta-annotation to create custom composed annotations with attribute overrides.

Placeholder Resolution in Statements

过去,元素中的占位符的值只能针对 JVM 系统属性或环境变量进行解析。现在不再是这样。由于 Environment 抽象集成在整个容器中,因此很容易通过它来路由占位符解析。这意味着您可以随心所欲地配置解析过程。您可以更改通过系统属性和环境变量进行搜索的优先级,或完全删除它们。您还可以根据需要将自己的属性源添加到组合中。

Historically, the value of placeholders in elements could be resolved only against JVM system properties or environment variables. This is no longer the case. Because the Environment abstraction is integrated throughout the container, it is easy to route resolution of placeholders through it. This means that you may configure the resolution process in any way you like. You can change the precedence of searching through system properties and environment variables or remove them entirely. You can also add your own property sources to the mix, as appropriate.

具体来说,以下语句无论 customer 属性在何处定义都没有关系,只要它在 Environment 中可用:

Concretely, the following statement works regardless of where the customer property is defined, as long as it is available in the Environment:

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