1.13 the Environment abstract

The Environment interface is abstractly integrated in a container, and the application Environment includes two important aspects: Profiles and Properties. A configuration file is a named logical group of bean definitions that is registered in the container only when a given configuration file is active. Beans can be assigned to configuration files, regardless of whether they are defined in XML or annotations. The role of the environment objects associated with profiles is to determine which profiles (if any) are currently active and which profiles (if any) should be active by default.

Properties play an important role in almost every application and can come from multiple sources: Properties files, JVM system Properties, system environment variables, JNDI, servlet context parameters, Properties objects, Map objects, and so on. The role of the Environment object is associated with properties to provide users with a convenient service interface to configure property sources and resolve properties.

1.13.1 Beans define Profiles

The bean definition properties file provides a mechanism in the core container that allows different beans to be registered in different environments. The word environment can mean different things to different users, and this feature helps in many usage scenarios, including:

  • Work on in-memory data sources during development rather than looking up the same data sources from JNDI during QA or production.
  • Use registered health infrastructure when deploying applications to the execution environment
  • Deploy and register custom bean implementations for clients A and B.

Consider the first usage scenario for a practical application, which requires obtaining a DataSource. In the test environment, the configuration assumptions are as follows:

@Bean
public DataSource dataSource(a) {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
Copy the code

Now consider how the application will be deployed to a QA or production environment, assuming that the application data source is registered in the generate application service JNDI directory. Our dataSource bean looks like this listing:

@Bean(destroyMethod="")
public DataSource dataSource(a) throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
Copy the code

The question is how to switch between using these two variants depending on your current environment. Over time, Spring users have devised a variety of ways to accomplish this task, often relying on a combination of system environment variables and XML statements containing ${placeholder} placeholders that are resolved to the correct configuration file path environment variables based on their values. The Bean definition configuration file is a core container feature that solves this problem.

If we summarize the use case shown in the previous environment-specific bean definition example, we end up registering a particular bean definition in a particular context, not in another. Suffice it to say that you want to register one configuration file defined by the bean in scenario A and another in scenario B. We started updating the configuration to reflect this need.

use@Profile

The @profile annotation allows you to specify which components are appropriate to de-register when one or more specified profiles are active. Using our previous example, we can rewrite the dataSource configuration as follows:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource(a) {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); }}Copy the code
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource(a) throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}Copy the code

As mentioned earlier, for @ Bean method, you typically use programmatic JNDI lookup, through the use of Spring JndiTemplate/JndiLocatorDelegate helper classes or used directly in front of the display of the JNDI InitialContext. Instead of using the JndiObjectFactoryBean variant, this forces you to declare the return type as a FactoryBean type.

The configuration file string may contain a simple configuration name (for example, production) or a configuration expression. A profile expression allows more complex configuration logic to be expressed (for example, production & US-east). The following operators are supported in profile expressions:

  • ! : the logic of
  • & : Logic and
  • | : logical or

You can’t mix & and! Operator without parentheses. For example, production & us – east | eu – central expression is invalid. It must be expressed similar production & (us – east | eu – central).

You can use @Profile as a metadata annotation to create your own custom annotations. The following example defines a custom @Production annotation that you can use as an alternative to @profile (” Production “).

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
Copy the code

If the @Configuration class is annotated @profile, all @bean methods and the classes associated with the @import annotation will be bypassed unless one or more specified Configuration files are activated. If @Component or @Configuration is tagged @profile ({” P1 “, “p2”}), the class will not be registered or processed unless Configuration files P1 or P2 are activated. If the configuration is prefixed with the NOT operator (!) The annotation element is registered only if the configuration file is not active. For example, @profile ({“p1”, “! P2 “}), registration will only occur if configuration P1 is active or if configuration P2 is not active.

@profile can also be declared at the method level to contain a specific configuration bean class (for example, to substitute for a specific bean), as shown in the following example:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") / / 1
    public DataSource standaloneDataSource(a) {
        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(a) throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }}Copy the code
  1. standaloneDataSourceThe method is onlydevelopmentValid in configuration
  2. jndiDataSourceThe method is onlyproductionValid in configuration

For @profile on @Bean methods, a special scenario can apply: in the case of an @bean method with the same Java method name overridden (similar to constructor overloading), the @profile condition needs to be declared consistently on all overloaded methods. If the condition is inconsistent, only the condition declared first in the overloaded method is important. Therefore, @profile cannot be used to select signature overloaded methods with specific parameters. At creation time, parsing between all factory methods of the same bean follows Spring’s constructor parsing method.

If you want to define beans with different configuration conditions, use different Java method names by using the @bean Name attribute to point to beans with the same name, as shown in the previous example. This is the only way to represent this arrangement in a valid Java class (since there can only be one method with a specific name and parameter signature) if the arguments are identical in front of each other (for example, all variants have parameterless constructors).

XML beans define configuration files

The XML counterpart is the profile attribute of the element. The same configuration we did earlier can be overridden in two XML files, similar to the following:

<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>
Copy the code
<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>
Copy the code

It is also possible to avoid splitting and nesting elements in the same file, as shown in the following example:

<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>
Copy the code

Spring-bean.xsd has been restricted to only allowing these elements as the last in the file. This should help provide flexibility without causing confusion in the XML file.

The XML counterpart does not support the configuration file expression described earlier: however, it may pass! Operator disallows a configuration file. It may also apply logical and by embedding configuration files, of the type shown in the following example:

<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>
Copy the code

In the previous example, the dataSource bean is exposed if both the Production and US-East configurations are activated.

To activate the profile

Now that we have updated the configuration file, we still need to indicate to Spring which configuration file is active. If we have started our application, we will see a NoSuchBeanDefinitionException throw, because the container can’t find the name of the dataSource bean.

Configuration files can be activated in a number of ways, but the most direct way is programmatically configured through the Environment API available through ApplicationContext. The following example shows how to do this:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
Copy the code

Alternatively, you can declaratively activate configuration files via the spring.profiles. Active property, as well as system environment variables, JVM properties, servlet context parameters in web.xml, and even as entries in JNDI (see the PropertySource abstraction). In integration testing, ActiveProfiles can be declared in the spring-test module by using @activeprofiles (view context configuration through the environment profile).

Note that configuration files 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 can accept String… Variable parameter. The following example activates multiple profiles:

ctx.getEnvironment().setActiveProfiles("profile1"."profile2");
Copy the code

Declaratively, spring.profiles.active can receive a comma-separated list of configuration names, as shown in the following example:

-Dspring.profiles.active="profile1,profile2"
Copy the code

The default profile

Default profile Indicates the profile that is enabled by default. Consider the following example:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource(a) {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql") .build(); }}Copy the code

If no profile is activated, the dataSource instance is created. You can see that this approach provides a default definition for one or more beans. If any profile is active, the default profile is not used.

You can change the default profile name by using setDefaultProfiles() on the Environment or, declaratively, by using the spring.profiles.default property.

1.13.2 PropertySourceabstract

Spring’s Environment abstraction provides search operations on configurable attribute source hierarchies. Consider the following list:

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);
Copy the code

In the previous snippet, we saw a high-level way to ask Spring if the my-property property is defined in the current Environment: to answer the question, the Environment object performs a search for the PropertySource collection object. PropertySource is a simple key-value abstraction of the source, and Spring’s StandardEnvironment is configured with two PropertySource objects, One describes a collection of JVM System (System.getProperties()) properties and the other describes a collection of System environment variables (System.getenv()).

These default attribute sources are StandardEnvironment compliant for use in standalone applications. StandardServletEnvironment is by attaching the default attribute source filling, including the servlet configuration and parameters of the servlet context. It can optionally enable JndiPropertySource. View javadock details.

Specifically, when you use StandardEnvironment, calling enp.containsProperty (“my-property”) will return true if the my-property system property or the my-Property environment variable is described at runtime.

The search performed is hierarchical, with system attributes taking precedence over environment variables by default. Therefore, if the my-property property is set in two places, the system property value will be returned when env.getProperty(“my-property”) is called. Note that attribute values are not merged, but are completely overwritten by previous values.

For general StandardServletEnvironment, complete hierarchy as follows, the highest priority items at the top:

1.ServletConfig parameter (if used – for example, if DispatcherServlet context)

2.ServletContext parameter (web.xml context parameter)

JNDI environment variables (Java :comp/env/)

4.JVM system parameters (-d command line parameters)

5.JVM system variables (operating system environment variables)

Most importantly, the entire mechanism is configurable. Maybe you have a custom attribute source that you want to integrate into this search. To do so, implement and instantiate your PropertySource and add it to the current Environment PropertySources collection. The following example shows how to do this:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
Copy the code

In the previous code, MyPropertySource was added to the highest index priority. If it contains a my-property property, it is detected and returned, supporting the my-property property in any other PropertySource. MutablePropertySourcesAPI exposes some methods, these methods allowed to precise action attribute the source collection.

1.13.3 use@PropertySource

The @propertysource annotation provides a convenient and declarative mechanism to add PropertySource to Spring’s Environment.

Given a file named app.properties that contains the key pair testBean.name =myTestBean, the @Configuration class below uses @propertysource, Call testbean.getName () this way and return myTestBean:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean(a) {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        returntestBean; }}Copy the code

Any ${} placeholders that appear in the @propertysource resource location will be resolved against the PropertySource set already registered in the environment, as shown in the following example:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean(a) {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        returntestBean; }}Copy the code

Given that my.placeholder is described in a property source that has already been registered (for example, a system property or environment variable), the placeholder is resolved to the corresponding value. If not, a default value, default/path, is used. If no default value is specified and the attribute cannot be resolved, an IllegalArgumentException is thrown.

According to the Java8 convention, @propertysource annotations are repeatable. However, all @propertysource annotations need to be declared at the same level, either directly on the configuration class or as metadata annotations in the same custom annotations. Mixing direct and meta-annotations is not recommended, as direct annotations effectively override meta-annotations.

Reference code: com. Liyong. Ioccontainer. Starter. EnvironmentIocContainer

1.13.4 Placeholder parsing in statements

Previously, placeholder values in elements could only be resolved based on JVM system attributes or environment variables. That is no longer the case. Because the Environment abstraction is integrated into the container, it is easy to route placeholder parsing through it. This means you can configure the parsing process any way you like. You can change the priority of search system properties and environment variables, or remove them entirely. You can also add your own attribute sources to the composition as appropriate.

Specifically, wherever the Customer attribute is defined, as long as it is available in the environment, the following statements will work:

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

The author

Personally engaged in the financial industry, I have worked in chongqing’s first-class technical team of Yiji Pay, Sijian Technology and an online car hailing platform, and now I am working in a bank responsible for the construction of unified payment system. I have a strong interest in the financial industry. It also practices big data, data storage, automated integration and deployment, distributed microservices, responsive programming, and artificial intelligence. At the same time, he is also keen on technology sharing, creating public accounts and blog sites to share knowledge system.

Blog: Youngitman.tech

CSDN:blog.csdn.net/liyong10288…

Wechat Official Account:

Technical Exchange Group: