Environment Abstraction
1.13. Environment Abstraction
The Environment
interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.
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.
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.
1.13.1. Bean Definition Profiles
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.
- 在开发中使用内存中的数据源,而在QA或生产中从JNDI中查找相同的数据源。
- 只有在将应用程序部署到性能环境时才注册监视基础设施。
- 为客户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:
@Bean
public DataSource dataSource() {
return new 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:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/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.
${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. 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
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:
@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("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
As mentioned earlier, with
@Bean
methods, you typically choose to use programmatic JNDI lookups, by using either Spring’sJndiTemplate
/JndiLocatorDelegate
helpers or the straight JNDIInitialContext
usage shown earlier but not theJndiObjectFactoryBean
variant, which would force you to declare the return type as theFactoryBean
type.
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
- !:配置文件的逻辑“不”
- &:配置文件的逻辑“和”
- |:概要文件的逻辑“或”
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 asproduction & (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")
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface 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.
@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:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
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")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
- The
standaloneDataSource
method is available only in thedevelopment
profile. - The
jndiDataSource
method is available only in theproduction
profile.
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).
如果您想定义具有不同概要文件条件的替代bean,可以使用不同的Java方法名,通过使用@Bean name属性指向相同的bean名,如前面的示例所示。如果参数签名都是相同的(例如,所有变量都有无参数的工厂方法),那么这是首先在有效Java类中表示这种安排的唯一方法(因为一个特定名称和参数签名只能有一个方法)。
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>
元素的概要文件属性。我们前面的示例配置可以在两个XML文件中重写,如下所示:
<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>
元素,如下例所示:
<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.
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>
In the preceding example, the
dataSource
bean is exposed if both theproduction
andus-east
profiles are active.
<!-- other bean definitions -->
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
在前面的示例中,如果生产和us-east概要文件都是活动的,那么dataSource bean就会公开。
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
.
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:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.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).
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:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
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 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();
}
}
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.
You can change the name of the default profile by using setDefaultProfiles()
on the Environment
or, declaratively, by using the spring.profiles.default
property.
1.13.2. `PropertySource` Abstraction
Spring’s Environment
abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing:
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);
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
, for use in standalone applications.StandardServletEnvironment
is populated with additional default property sources including servlet config and servlet context parameters. It can optionally enable aJndiPropertySource
. See the javadoc for details.
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
my-property
property happens to be set in both places during a call toenv.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.For a common
StandardServletEnvironment
, the full hierarchy is as follows, with the highest-precedence entries at the top:
- ServletConfig parameters (if applicable — for example, in case of a
DispatcherServlet
context)- ServletContext parameters (web.xml context-param entries)
- JNDI environment variables (
java:comp/env/
entries)- JVM system properties (
-D
command-line arguments)- JVM system environment (operating system environment variables)
对于一个通用的StandardServletEnvironment,完整的层次结构如下所示,优先级最高的条目位于顶部:
- ServletConfig参数(如果适用——例如,DispatcherServlet上下文)
- ServletContext参数(web.xml上下文参数条目)
- JNDI环境变量(java:comp/env/ entries)
- JVM系统属性(-D命令行参数)
- 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:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new 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.
1.13.3. Using `@PropertySource`
The @PropertySource
annotation provides a convenient and declarative mechanism for adding a PropertySource
to Spring’s 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
:
@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;
}
}
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;
}
}
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.
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. Mixing direct annotations and meta-annotations is not recommended, since direct annotations effectively override meta-annotations.
1.13.4. Placeholder Resolution in Statements
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.
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>