From 15f496bc2a2b51916907eec34d7eabe72bd60b69 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 25 Aug 2014 15:26:26 +0200 Subject: [PATCH] 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 --- src/asciidoc/index.adoc | 466 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 449 insertions(+), 17 deletions(-) diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 4192d79f039..63dc28cef45 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -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: <> +and <>. + +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 `` 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"] +---- + + + + + + + +---- + +[source,xml,indent=0] +[subs="verbatim,quotes"] +---- + + + + +---- + +It is also possible to avoid that split and nest `` 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"] +---- + + + + + + + + + + + + + + + +---- + +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 +<>). + +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 <> 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"] +---- + + + +---- [[context-load-time-weaver]]