1.13. Environment 抽象

预计阅读时间: 26 分钟

The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties. 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. 配置文件是一个命名逻辑组,包含要注册到容器的 bean 定义,仅当指定的配置文件处于活动状态时。bean 可以分配给配置文件,无论其是在 XML 中定义的还是通过注解定义的。 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. 属性在几乎所有应用程序中都扮演着重要角色,可能来源于多种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、临时 Properties 对象、 Map 对象等等。 Environment 对象与属性相关的角色是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。

(#beans-definition-profiles)1.13.1. Bean Definition Profiles

1.13.1. 豆定义配置文件

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: Bean 定义配置为核心容器提供了一个机制,允许在不同环境中注册不同的 Bean。这个词“环境”对不同用户可能有不同的含义,这个特性可以帮助处理许多用例,包括:

  • Working against an in-memory datasource in development versus looking up that same datasource from JNDI when in QA or production. 与开发中针对内存数据源工作相比,在 QA 或生产环境中从 JNDI 查找相同的数据源。

  • Registering monitoring infrastructure only when deploying an application into a performance environment. 仅当将应用程序部署到性能环境时注册监控基础设施。

  • Registering customized implementations of beans for customer A versus customer B deployments. 注册针对客户 A 和客户 B 部署的定制化 bean 实现。

Consider the first use case in a practical application that requires a DataSource. In a test environment, the configuration might resemble the following: 考虑一个实际应用中需要使用 DataSource 的第一个用例。在测试环境中,配置可能类似于以下内容:

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

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: 现在考虑如何将此应用程序部署到 QA 或生产环境,假设应用程序的数据源已在生产应用程序服务器的 JNDI 目录中注册。我们的 dataSource Bean 现在看起来如下所示:

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

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. 问题是如何在当前环境中在这两种变体之间切换。随着时间的推移,Spring 用户已经想出了一些完成这项任务的方法,通常依赖于系统环境变量和包含 ${placeholder} 标记的 XML <import/> 语句的组合,这些标记根据环境变量的值解析到正确的配置文件路径。Bean 定义配置文件是核心容器功能,它为解决这个问题提供了一个解决方案。

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. 如果我们泛化前面示例中环境特定 bean 定义的使用情况,我们最终会面临需要在某些上下文中注册某些 bean 定义,但在其他上下文中则不需要的情况。 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. 你可以说,在情况 A 中你想注册某种 Bean 定义的配置文件,而在情况 B 中则注册不同的配置文件。我们首先更新我们的配置以反映这一需求。

(#beans-definition-profiles-java)Using@Profile 使用@Profile

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: @Profile 注解允许你指示当一个或多个指定的配置文件处于活动状态时,组件可注册。使用我们之前的示例,我们可以将 dataSource 配置重写如下:

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

1

The standaloneDataSource method is available only in the development profile. standaloneDataSource 方法仅在 development 配置文件中可用。

2

The jndiDataSource method is available only in the production profile. jndiDataSource 方法仅在 production 配置文件中可用。

@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="") public DataSource dataSource() 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. standaloneDataSource 方法仅在 development 配置文件中可用。

2

The jndiDataSource method is available only in the production profile. jndiDataSource 方法仅在 production 配置文件中可用。

@Configuration @Profile("production") class JndiDataConfig { @Bean(destroyMethod = "") fun dataSource(): DataSource { val ctx = InitialContext() return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource } }

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. 如前所述,使用 @Bean 方法时,你通常会选择使用程序化 JNDI 查找,通过使用 Spring 的 JndiTemplate / JndiLocatorDelegate 辅助工具或之前展示的直接 JNDI InitialContext 使用方法,而不是 JndiObjectFactoryBean 变体,这会强制你将返回类型声明为 FactoryBean 类型。

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

  • !: A logical “not” of the profile ! :配置的逻辑“非”

  • &: A logical “and” of the profiles & :配置文件的逻辑“与”

  • |: A logical “or” of the profiles | :配置文件的逻辑“或”

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). 你不能在不使用括号的情况下混合使用 &| 运算符。例如, production & us-east | eu-central 不是一个有效的表达式。它必须表示为 production & (us-east | eu-central)

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"): 你可以使用 @Profile 作为创建自定义组合注解的元注解。以下示例定义了一个自定义的 @Production 注解,你可以用它作为 @Profile("production") 的直接替换:

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

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. 如果一个类被标记为 @Configuration ,除非指定的配置文件之一处于活动状态,否则与该类关联的所有 @Bean 方法和 @Import 注解都将被绕过。如果一个 @Component@Configuration 类被标记为 @Profile({"p1", "p2"}) ,除非已激活配置文件'p1' 或'p2',否则该类不会被注册或处理。如果给定的配置文件以 NOT 运算符( ! )为前缀,则只有当配置文件未激活时,注解元素才会被注册。例如,给定 @Profile({"p1", "!p2"}) ,如果配置文件'p1'处于活动状态或配置文件'p2'未激活,则将发生注册。

@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: @Profile 也可以在方法级别声明,以仅包含配置类中的一个特定 bean(例如,对于特定 bean 的替代变体),如下例所示:

@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"); } }
@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 }

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.

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

(#beans-definition-profiles-xml)XML Bean Definition Profiles

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>

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>

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. spring-bean.xsd 已被限制,只允许将此类元素作为文件中的最后几个。这应该有助于提供灵活性,而不会在 XML 文件中造成杂乱。

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

In the preceding example, the dataSource bean is exposed if both the production and us-east profiles are active. 在先前的示例中,如果 productionus-east 配置文件都处于活动状态,则 dataSource 豆将被公开。

(#beans-definition-profiles-enable)Activating a Profile 激活配置文件

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. 现在我们已经更新了我们的配置,我们仍然需要指导 Spring 哪个配置文件是激活的。如果我们现在启动我们的示例应用程序,我们会看到抛出 NoSuchBeanDefinitionException ,因为容器找不到名为 dataSource 的 Spring Bean。

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: 激活配置文件可以通过多种方式完成,但最直接的方法是通过对 Environment API 进行编程操作,该 API 可通过 ApplicationContext 获取。以下示例展示了如何进行操作:

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

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). 此外,你还可以通过 spring.profiles.active 属性声明式地激活配置文件,这可能通过系统环境变量、JVM 系统属性、 web.xml 中的 servlet 上下文参数或甚至作为 JNDI 中的条目(见 PropertySource 抽象)来指定。在集成测试中,可以通过在 spring-test 模块中使用 @ActiveProfiles 注解来声明活动配置文件(见带有环境配置的上下文配置)。

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: 请注意,配置文件不是一个“非此即彼”的选择。你可以同时激活多个配置文件。在程序上,你可以向 setActiveProfiles() 方法提供多个配置文件名称,该方法接受 String…​ 可变参数。以下示例激活了多个配置文件:

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

Declaratively, spring.profiles.active may accept a comma-separated list of profile names, as the following example shows: 声明性上, spring.profiles.active 可以接受以逗号分隔的配置文件名称列表,如下例所示:

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

(#beans-definition-profiles-default)Default Profile 默认配置文件

The default profile represents the profile that is enabled by default. Consider the following example: 默认配置文件表示默认启用的配置文件。考虑以下示例:

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

If no profile is active, 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. 如果没有活动配置文件,将创建 dataSource 。你可以将此视为为一个或多个 bean 提供默认定义的方式。如果启用了任何配置文件,则默认配置文件不适用。

You can change the name of the default profile by using setDefaultProfiles() on the Environment or, declaratively, by using the spring.profiles.default property. 你可以通过使用 setDefaultProfiles()Environment 上或声明性地使用 spring.profiles.default 属性来更改默认配置文件名称。

(#beans-property-source-abstraction)1.13.2.PropertySource Abstraction 1.13.2.PropertySource 抽象

Spring’s Environment abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing: 春季的 Environment 抽象提供了对可配置属性源层次结构的搜索操作。考虑以下列表:

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

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()). 在前面这段代码中,我们看到一种询问 Spring 当前环境是否定义了 my-property 属性的高级方法。为了回答这个问题, Environment 对象在一系列 PropertySource 对象上执行搜索。 PropertySource 是一个简单的抽象,覆盖了任何键值对来源,Spring 的 StandardEnvironment 配置了两个 PropertySource 对象——一个代表 JVM 系统属性集( System.getProperties() ),另一个代表系统环境变量集( System.getenv() )。

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 中,用于独立应用程序。 StandardServletEnvironment 包含额外的默认属性源,包括 servlet 配置、servlet 上下文参数,以及如果 JNDI 可用,则包含 JndiPropertySource

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

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. 搜索操作是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用 env.getProperty("my-property") 时, my-property 属性在两个地方都被设置,系统属性值“获胜”并返回。请注意,属性值不是合并,而是被前面的条目完全覆盖。

For a common StandardServletEnvironment, the full hierarchy is as follows, with the highest-precedence entries at the top: 对于一个常见的 StandardServletEnvironment ,完整的层次结构如下,优先级最高的条目在最上面:

  1. ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context) ServletConfig 参数(如适用——例如,在 DispatcherServlet 上下文中)

  2. ServletContext parameters (web.xml context-param entries) ServletContext 参数(web.xml 的 context-param 条目)

  3. JNDI environment variables (java:comp/env/ entries) JNDI 环境变量( java:comp/env/ 条目)

  4. JVM system properties (-D command-line arguments) JVM 系统属性( -D 命令行参数)

  5. JVM system environment (operating system environment variables) JVM 系统环境(操作系统环境变量)

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

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

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. 在先前的代码中, MyPropertySource 被添加到搜索中,具有最高优先级。如果它包含一个 my-property 属性,则会检测并返回该属性,优先于任何其他 PropertySource 中的 my-property 属性。 MutablePropertySources API 提供了多种方法,允许精确操作属性源集合。

(#beans-using-propertysource)1.13.3. Using@PropertySource 1.13.3. 使用@PropertySource

The @PropertySource annotation provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment. @PropertySource 注解提供了一种方便且声明式的机制,用于向 Spring 的 PropertySource 添加 Environment

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: 给定一个名为 app.properties 的文件,该文件包含键值对 testbean.name=myTestBean ,以下 @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; } }
@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")!! } }

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

@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")!! } }

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. 假设 my.placeholder 已存在于已注册的一个属性源中(例如系统属性或环境变量),则占位符解析为相应的值。如果没有,则使用 default/path 作为默认值。如果没有指定默认值且无法解析属性,则抛出 IllegalArgumentException

The @PropertySource annotation is repeatable, according to Java 8 conventions. However, all such @PropertySource annotations need to be declared at the same level, either directly on the configuration class or as meta-annotations within the same custom annotation. @PropertySource 注解是可重复的,根据 Java 8 约定。然而,所有此类 @PropertySource 注解都需要在同一级别声明,要么直接在配置类上,要么作为同一自定义注解内的元注解。 Mixing direct annotations and meta-annotations is not recommended, since direct annotations effectively override meta-annotations. 混合直接注释和元注释不建议,因为直接注释会有效覆盖元注释。

(#beans-placeholder-resolution-in-statements)1.13.4. Placeholder Resolution in Statements

1.13.4. 语句中的占位符解析

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. 历史上,元素中占位符的值只能通过 JVM 系统属性或环境变量来解析。这种情况已经不再存在。因为 Environment 抽象在整个容器中集成,所以通过它来路由占位符解析变得很容易。这意味着你可以按任何你喜欢的配置解析过程。 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. 你可以更改通过系统属性和环境变量搜索的优先级,或者完全删除它们。你还可以根据需要添加自己的属性源。

Concretely, the following statement works regardless of where the customer property is defined, as long as it is available in the Environment: 具体来说,以下语句无论 customer 属性定义在哪里,只要它在 Environment 中可用,都会生效

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