Environment Abstraction
Environment
接口是集成在容器中的一个抽象,它对应用程序环境的两个关键方面进行建模: profiles 和 properties。
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.
|
2 | @Bean(destroyMethod = "") disables default destroy method inference. |
如前文所述,对于 @{48} 方法,你通常选择使用Spring的@{49}/@{50}帮助程序或前面演示了直接JNDI @{51}使用法,通过编程的方式JNDI 查找,但不是@{52}变量,它会强制你将返回类型声明为@{53}类型。 |
As mentioned earlier, with |
配置文件字符串可能包含一个简单的配置文件名(例如,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 logicalNOT
of the profile -
&
: A logicalAND
of the profiles -
|
: A logicalOR
of the profiles
你不能混合使用@{54}和@{55}运算符而不使用括号。例如,@{56}不是一个有效的表达式。它必须表示为@{57}。 |
You cannot mix the |
你可以使用 @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 |
@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.
|
3 | The standaloneDataSource method is available only in the development profile. |
4 | The jndiDataSource method is available only in the production profile. |
使用 With 如果你想使用不同的配置文件条件定义备用 Bean,请使用 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 |
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 等效项不支持前面描述的配置文件表达式。但是,可以通过使用 The XML counterpart does not support the profile expressions described earlier. It is possible,
however, to negate a profile by using the
在前面的示例中,如果 In the preceding example, the |
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()
).
这些默认属性源适用于 |
These default property sources are present for |
具体来说,当您使用 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.
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用 The search performed is hierarchical. By default, system properties have precedence over
environment variables. So, if the 对于常见的 For a common
|
最重要的是,整个机制是可配置的。也许您有一个自定义属性源,希望将其集成到此搜索中。为此,请实现并实例化您自己的 PropertySource
,并将其添加到当前 Environment
的 PropertySources
集合中。以下示例展示了如何执行此操作:
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.
|
|
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>