Browse Source

Document environment abstraction

This commit replace the two empty "chapters" dedicated to profiles
and property source to an 'Environment abstraction' chapter that
covers both.

Issue: SPR-12107
pull/636/head
Stephane Nicoll 10 years ago
parent
commit
15f496bc2a
  1. 466
      src/asciidoc/index.adoc

466
src/asciidoc/index.adoc

@ -7740,36 +7740,468 @@ jdbc.password= @@ -7740,36 +7740,468 @@ jdbc.password=
}
----
[[beans-environment]]
=== Environment abstraction
The {javadoc-baseurl}/org/springframework/core/env/Environment.html[`Environment`]
is an abstraction integrated in the container that models two key
aspects of the application environment: <<beans-definition-profiles,_profiles_>>
and <<beans-property-source-abstraction,_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 via 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, Maps, 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.
[[beans-definition-profiles]]
=== Bean definition profiles and environment abstraction
==== Bean definition profiles
Bean definition profiles is a mechanism in the core container that allows for registration
of different beans in different environments. This feature can help with many use cases,
including:
Bean definition profiles is 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 vs 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 vs. customer B deployments
* registering customized implementations of beans for customer A vs. customer
B deployments
Let's consider the first use case in a practical application that requires a
`DataSource`. In a test environment, the configuration may look like this:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
----
Let's now consider how this application will be deployed into a QA or production
environment, assuming that the datasource for the application will be registered
with the production application server's JNDI directory. Our `dataSource` bean
now looks like this:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Bean
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.
If we generalize the example use case above of environment-specific bean
definitions, we end up with the need to register certain bean definitions in
certain contexts, while 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. Let's first see how we can update our configuration to reflect
this need.
[[beans-definition-profiles-java]]
===== @Profile
The {javadoc-baseurl}/org/springframework/context/annotation/Profile.html[`@Profile`]
annotation allows to indicate that a component is eligible for registration
when one or more specified profiles are active. Using our example above, we
can rewrite the _dataSource_ configuration as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
**@Profile("dev")**
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();
}
}
----
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
**@Profile("production")**
public class JndiDataConfig {
@Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
----
`@Profile` can be used as a meta-annotation, for the purpose of composing
custom stereotype annotations. The following example defines a `@Production`
custom annotation that can be used as a drop-in replacement of
`@Profile("production")`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
**@Profile("production")**
public @interface Production {
}
----
`@Profile` can also be specified at method-level to include only one particular
bean of a configuration class:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Configuration
public class AppConfig {
@Bean
**@Profile("dev")**
public DataSource devDataSource() {
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
**@Profile("production")**
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
----
[TIP]
====
If a `@Configuration` class is marked with `@Profile`, all of the `@Bean` methods
and `@Import` annotations associated with that class will be 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 will not be registered/
processed unless profiles 'p1' and/or 'p2' have been activated. If a given profile
is prefixed with the NOT operator (`!`), the annotated element will be registered
if the profile is **not** active. e.g., for `@Profile({"p1", "!p2"})`, registration
will occur if profile 'p1' is active or if profile 'p2' is not active.
====
[[beans-definition-profiles-xml]]
==== XML Bean definition profiles
The XML counterpart is an update of the `beans` element that accepts a
`profile` attribute. Our sample configuration above can be rewritten in two XML
files as follows:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<beans profile="dev"
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>
----
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<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:
Find out more about https://spring.io/blog/2011/02/11/spring-framework-3-1-m1-released/[Environment,
XML Profiles] and the
https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/[@Profile annotation].
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<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="dev">
<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.
[[beans-definition-profiles-enable]]
===== Enabling a profile
Now that we have updated our configuration, we still need to instruct 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 `ApplicationContext` API:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
----
In addition, profiles may also be activated declaratively 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
<<beans-property-source-abstraction>>).
Note that profiles are not an "either-or" proposition; it is possible to activate multiple
profiles at once. Programmatically, simply provide multiple profile names to the
`setActiveProfiles()` method, which accepts `String...` varargs:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
----
Declaratively, `spring.profiles.active` may accept a comma-separated list of profile names:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
-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:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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` above will be created; this can be
seen as a way to provide a _default_ definition for one or more beans. If any
profile is enabled, the _default_ profile will not apply.
The name of that default profile can be changed using `setDefaultProfiles` on
the `Environment` or declaratively using the `spring.profiles.default` property.
[[beans-property-source-abstraction]]
=== PropertySource Abstraction
==== PropertySource Abstraction
Spring's Environment abstraction provides search operations over a configurable
hierarchy of property sources. To explain fully, consider the following:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
----
In the snippet above, we see a high-level way of asking Spring whether the `foo` property is
defined for the current environment. To answer this question, the `Environment` object performs
a search over a set of {javadoc-baseurl}/org/springframework/core/env/PropertySource.html[`PropertySource`]
objects. A `PropertySource` is a simple abstraction over any source of key-value pairs, and
Spring's {javadoc-baseurl}/org/springframework/core/env/StandardEnvironment.html[`StandardEnvironment`]
is configured with two PropertySource objects -- one representing the set of JVM system properties
(_a la_ `System.getProperties()`) and one representing the set of system environment variables
(_a la_ `System.getenv()`).
[NOTE]
====
These default property sources are present for `StandardEnvironment`, for use in standalone
applications. {javadoc-baseurl}/org/springframework/web/context/support/StandardServletEnvironment.html[`StandardServletEnvironment`]
is populated with additional default property sources including servlet config and servlet
context parameters. {javadoc-baseurl}/org/springframework/web/portlet/context/StandardPortletEnvironment.html[`StandardPortletEnvironment`]
similarly has access to portlet config and portlet context parameters as property sources.
Both can optionally enable a {javadoc-baseurl}/org/springframework/jndi/JndiPropertySource.html[`JndiPropertySource`].
See Javadoc for details.
====
Concretely, when using the `StandardEnvironment`, the call to `env.containsProperty("foo")`
will return true if a `foo` system property or `foo` environment variable is present at
runtime.
[TIP]
====
The search performed is hierarchical. By default, system properties have precedence over
environment variables, so if the `foo` property happens to be set in both places during
a call to `env.getProperty("foo")`, the system property value will 'win' and be returned
preferentially over the environment variable.
====
Most importantly, the entire mechanism is configurable. Perhaps you have a custom source
of properties that you'd like to integrate into this search. No problem -- simply implement
and instantiate your own `PropertySource` and add it to the set of `PropertySources` for the
current `Environment`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
----
Spring's <<beans-definition-profiles,Environment abstraction>> provides search operations
over a configurable hierarchy of property sources.
In the code above, `MyPropertySource` has been added with highest precedence in the
search. If it contains a `foo` property, it will be detected and returned ahead of
any `foo` property in any other `PropertySource`. The
{javadoc-baseurl}/org/springframework/core/env/MutablePropertySources.html[`MutablePropertySources`]
API exposes a number of methods that allow for precise manipulation of the set of
property sources.
You can find out more about
https://spring.io/blog/2011/02/15/spring-3-1-m1-unified-property-management/[Unified
Property Management], the
{javadoc-baseurl}/org/springframework/core/env/PropertySource.html[`PropertySource` class]
and the {javadoc-baseurl}/org/springframework/context/annotation/PropertySource.html[`@PropertySource`
annotation].
==== @PropertySource
The {javadoc-baseurl}/org/springframework/context/annotation/PropertySource.html[`@PropertySource`]
annotation provides a convenient and declarative mechanism for adding a `PropertySource`
to Spring's `Environment`.
Given a file "app.properties" containing the key/value pair `testbean.name=myTestBean`,
the following `@Configuration` class uses `@PropertySource` in such a way that
a call to `testBean.getName()` will return "myTestBean".
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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 will
be resolved against the set of property sources already registered against the
environment. For example:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@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, e.g. system properties or environment variables, the placeholder will
be resolved to the corresponding value. If not, then "default/path" will be used
as a default. If no default is specified and a property cannot be resolved, an
`IllegalArgumentException` will be thrown.
==== Placeholder resolution in statements
Historically, the value of placeholders in elements could be resolved only against
JVM system properties or environment variables. No longer is this the case. Because
the Environment abstraction is integrated throughout the container, it's easy to
route resolution of placeholders through it. This means that you may configure the
resolution process in any way you like: change the precedence of searching through
system properties and environment variables, or remove them entirely; 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`:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
----
[[context-load-time-weaver]]

Loading…
Cancel
Save