spring - use profile to manage environment information

Keywords: Programming JDBC Spring Java SQL

Programs generally have development environment, test environment and online environment. The basis of program running in these environments is generally different. For example, in a program with data source access, embedded database may be used in development, and independent mysql may be used in the test environment. After the program is officially launched, it will be different. Not only the connection information of data source is related to the environment, but also the connection information of data source Many other program dependent property values are also environment dependent. It is better to be able to handle the differences between different environments flexibly when writing programs. We hope that if the program runs in the development machine, the configuration value of embedded database will be automatically enabled, and if it runs in the production environment, the configuration value of online database connection will be automatically applied. In the previous old projects of the company, the separation of environment variable information was not done well, which led to the manual modification of the program configuration every time the program was deployed in different environments, which was very difficult, and manual operation meant low efficiency and error prone.

At present, I'm afraid that many java projects need to use Spring for development, and Spring also supports the ability of flexible management of information related to environment dependence. Therefore, we should give Spring the management of configuration like data source connection.

Environment

Environment is an abstract component introduced in spring 3.0. Environment can flexibly make certain conditions match before registering bean s into IoC containers. Environment can also manage the underlying property resources in a unified way, that is, environment encapsulates two aspects: profiles and properties.

For profile, it can be understood that profile is associated with the definition of a group of beans. This group of beans will be registered in the IoC container only when the profile meets the conditions. Profile has a name, which usually reflects the current environment, such as development, test or production.

Property refers to the property value in the form of "key value", which is a configuration form used by most programs. The properties here can come from many places, such as properties file, jvm system property, operating system Environment property, jndi property, servlet The context parameter can also be the map structure in memory. The Environment object will manage these attribute information.

Define profile

To define a profile is to declare a name to associate the definition of a group of beans. When the profile of the definition is active, the group of beans will be registered in the IoC container. For example, for programs involving data source access, the data source connection information of programs running in different environments such as development, testing, online, etc. is different. In this case, we can define three DataSource objects, which are associated with different profiles. The following configuration is an example of defining DataSource:

@Configuration
public class DataSourceConfig {
    @Profile("development")
    @Bean(destroyMethod = "close")
    public DataSource devDataSource() {
		// Develop and use an embedded database
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("init-schema.sql")
                .addScript("init-data.sql")
                .build();
    }

    @Profile("test")
    @Bean(destroyMethod = "close")
    public DataSource testDataSource() {
        BasicDataSource ds = new BasicDataSource();
        // mysql is used in the test environment
        ds.setUrl("jdbc:mysql://test-db-host:port/test-schema");
        ds.setUsername("root");
        ds.setPassword("root");
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return ds;
    }
	
    @Profile("production")
    @Bean(destroyMethod = "close")
    public DataSource prodDataSource() throws NamingException {
        Context context = new InitialContext();
        return (DataSource) context.lookup("java:comp/env/jdbc/datasource");
    }
}

The @ profile annotation is used to define a profile. The above configuration code defines different profiles for development, test and online environment respectively. The name of the profile also reflects the environment. The xml configuration equivalent to the above configuration is as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <!-- Other bean Definition -->

  <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="test">
  	<bean id="dataSoutce" class="org.apache.commons.dbcp2.BasicDataSource" 
          destroy-method="close">
    	<property name="url" value="jdbc:mysql://test-db-host:port/test-schema"/>
      <property name="username" value="root"/>
      <property name="password" value="root"/>
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    </bean>
  </beans>
  
  <besns profile="production">
    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
  </besns>
</beans>

Activate profile

The effect we want is that if the application is running on the development machine, the DataSource object injected into the program should be the DataSource defined in the profile "development". Similarly, if it is running in the test environment or production environment, the DataSource object registered in the IoC container should be the data defined in the profile "test" or "production", respectively Source, how can Spring know what kind of environment the program is running in at this time? We need to tell it clearly. Then environment can get the currently activated profile list through the agreed rules. Environment has the following methods to query the activated profile list:

public interface Environment extends PropertyResolver {
  // Get the active profiles in the current environment
  String[] getActiveProfiles();
  // Get the default profiles in the current environment. The default profiles are always active
  String[] getDefaultProfiles();

  // Determines whether the specified profile is explicitly specified as active, or is the default profile
  boolean acceptsProfiles(String... profiles);
}

Environment also has a sub interface, ConfigurableEnvironment, which provides several important methods to activate profile:

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
  // Specify the profile list to be activated in the current environment;
  // If this method parameter passes an empty profile list, all profiles that have been set to activate will be cleared if they already exist
  // For an activation list, you can add a profile to the list by using the addActiveProfile method below
  void setActiveProfiles(String... profiles);
  
  void addActiveProfile(String profile);

  // Set the default profile list. If there is no other specified profile, the default profile will be activated
  void setDefaultProfiles(String... profiles);
}

Now we can call these methods to activate our desired profile. ApplicationContext supports getting its associated Environment. We can get the Environment object of the IoC container first. Here is an example:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
Environment env = ctx.getEnvironment();
env.setActiveProfiles("production");
ctx.register(DataSourceConfig.class, OtherConfigurationClasses.class);
ctx.refresh();

In the actual Java EE project, we usually don't create the ApplicationContext object ourselves, so it's not common to activate the profile list through Java programming. Instead, we should configure the spring.profiles.active property to activate the profile. This property is defined internally in Spring, and should not be changed to another name. We can configure it as the jvm system property or the operating system ring The environment variable, or the ServletContext parameter in the web descriptor, or the jndi resource can be configured, for example, in the jvm system properties:

java AppMain -Dspring.profiles.active=production

Default profile

The meaning of default profile is that if there is no explicitly activated profile in the current environment, the default profile list configured in the program will be automatically activated. If there are other activated profiles, the default profile will not be activated. Here is how to define a default profile:

@Configuration
public class DataSourceConfig {
    // Use name default
    @Profile("default")
    @Bean(destroyMethod = "close")
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("init-schema.sql")
                .addScript("init-data.sql")
                .build();
    }
}

You can see that the profile named default becomes the default profile. If you want to change the default name of Spring's default profile, you can modify it through the setDefaultProfiles(...) method of ConfigurableEnvironment, or by configuring the spring.profiles.default property.

java AppMain -Dspring.profiles.default=default

PropertySource Abstract

Unified management of attribute resources is another important ability of Environment. Attribute is a very important part for almost all programs. In fact, the scenarios where we need to use profile are often due to the different values of attributes that the programs depend on when running in different environments.

PropertySource abstracts properties in the form of "key value". As mentioned before, PropertySource corresponds to a variety of actual underlying property value sources. Environment provides the ability to query all loaded properties in the current environment. No matter where the property source is, the ability of environment is obtained by inheriting the PropertyResolver interface. PropertyResolver can Query the relevant property values from the underlying property sources hierarchy.

Query whether there is "foo" in the current environment:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
String v = evn.getProperty("foo");
System.out.print(v);

Generally, an instance of StandardEnvironment type is obtained from ApplicationContext. Two PropertySource properties have been loaded in StandardEnvironment by default: jvm property and operating system property.

When searching property values from the Environment, they have different priorities for different propertysources. For example, properties in a general jvm have higher priorities than those in the operating system Environment.

Add properties to container

In addition to the list of properties loaded by Environment by default, we can add additional properties to the Environment object. We only need to implement a PropertySource ourselves, and then add it through the PropertySources under Environment management. The implementation code is as follows:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
ConfigurableEnvironment env = ctx.getEnvironment();
MutablePropertySources mps = env.getPropertySources();
mps.addFirst(new MyPropertySource());// addFirst is added as the highest priority, and addLast is added as the lowest priority

Add properties in configuration mode

For configuration, the @ PropertySource java annotation can also be used to add additional property lists to the Environment. For example, we have a "db.properties" property configuration file, which contains the connection information of jdbc data source. After referencing this property file through @ PropertySource, you can directly obtain the property values in the configuration class and other parts of the program:

@Configuration
@PropertySource("classpath:/db.properties") 
// You can also configure multiple @ propertysources
public class PropertySourcesAnnotation {
    @Autowired
    Environment env;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setUrl(env.getProperty("url"));
        ds.setUsername(env.getProperty("username "));
        ds.setPassword(env.getProperty("password"));
        ds.setDriverClassName(env.getProperty("driverClassName"));
        return ds;
    }
}

Resolve ${...} placeholders

Before the existence of Environment, the property placeholder "${property.placeholder}" used in the program can only parse the value of the property placeholder from two sources: jvm property and operating system Environment variable. After using Environment, we can flexibly configure the managed properties. We can add properties, delete properties, or modify the priority of properties, application Ncontext integrates Environment, so as long as the Environment can find the corresponding attribute value, the attribute placeholder can be replaced correctly.

Posted by nttaylor on Tue, 07 Apr 2020 22:17:24 -0700