[springboot] read the yml configuration file with the static method, which can be read dynamically according to different environments

Keywords: Java Spring Boot yml

There are many ways to read configuration files in springboot, which I will not describe here;

Background: there is a redis tool class in the common of the project. Because many modules use redisconfig, you don't want to write a redisconfig in each module, and the tool class is used. It is used in the way of static loading, but if you use the traditional reading method, it is as follows:

RedisConfig:

package com.xx.xx.common.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfig {

	// Redis server IP
	public static String IP;

	. . . . . . 

	public String getIP() {
		return IP;
	}

	@Value("${redis.ip}")
	public void setIP(String ip) {
		IP = ip;
	}
}

RedisUtil:

/**
 * Redis Tool class
 */

public class RedisUtil {

	// protected static ReentrantLock lockPool = new ReentrantLock();
	protected static final ReentrantLock REENTRANT_LOCK = new ReentrantLock();

	private static Logger _log = LoggerFactory.getLogger(RedisUtil.class);
	// Whether to validate in advance when a jedis instance is browsed; If true, all the jedis instances are available;
	private static boolean TEST_ON_BORROW = false;

	private static volatile JedisPool jedisPool = null;

	/**
	 * redis Expiration time in seconds
	 */
	public final static int EXRP_HOUR = 60 * 60; // an hour
	public final static int EXRP_DAY = 60 * 60 * 24; // one day
	public final static int EXRP_MONTH = 60 * 60 * 24 * 30; // one month

	static {
		try {
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(RedisConfig.MAX_ACTIVE);
			config.setMaxIdle(RedisConfig.MAX_IDLE);
			config.setMaxWaitMillis(RedisConfig.MAX_WAIT);
			config.setTestOnBorrow(TEST_ON_BORROW);
			jedisPool = new JedisPool(config, RedisConfig.IP, RedisConfig.PORT, RedisConfig.TIMEOUT);
		} catch (Exception e) {
			_log.error("First create JedisPool error : " + e);
		}
	}

	/**
	 * Initialize Redis connection pool
	 */
	private static void initialPool() {
		if (jedisPool != null) {
			try {
				JedisPoolConfig config = new JedisPoolConfig();
				config.setMaxTotal(RedisConfig.MAX_ACTIVE);
				config.setMaxIdle(RedisConfig.MAX_IDLE);
				config.setMaxWaitMillis(RedisConfig.MAX_WAIT);
				config.setTestOnBorrow(TEST_ON_BORROW);
				jedisPool = new JedisPool(config, RedisConfig.IP, RedisConfig.PORT, RedisConfig.TIMEOUT);
			} catch (Exception e) {
				_log.error("First create JedisPool error : " + e);
			}
		}
	}

	/**
	 * Synchronous initialization in a multithreaded environment
	 */
	private static synchronized void poolInit() {
		if (null == jedisPool) {
			initialPool();
		}
	}

	/**
	 * Get Jedis instance synchronously
	 *
	 * @return Jedis
	 */
	public synchronized static Jedis getJedis() {
		poolInit();
		Jedis jedis = null;
		try {
			if (null != jedisPool) {
				jedis = jedisPool.getResource();
				if (RedisConfig.PASSWORD != null) {
					jedis.auth(RedisConfig.PASSWORD);
				}
				// When getting jedis, set dbindex add by liux 20191211
				jedis.select(RedisConfig.JEDIS_DB_INDEX);
			}
		} catch (Exception e) {
			_log.error("Get jedis error : " + e);
			_log.error(" ip-> " + RedisConfig.IP + " port-> " + RedisConfig.PORT);
		}
		return jedis;
	}

	/**
	 * Set String
	 *
	 * @param key
	 * @param value
	 * @param dbSelect
	 *            (Set DB library, pass null (db0 by default)
	 */
	public synchronized static void set(String key, String value, Integer dbSelect) {
		Jedis jedis = null;
		try {
			value = StringUtils.isBlank(value) ? "" : value;
			jedis = getJedis();
			jedis.select(dbSelect == null ? RedisConfig.JEDIS_DB_INDEX : dbSelect);
			jedis.set(key, value);
			jedis.expire(key, EXRP_MONTH);
		} catch (Exception e) {
			_log.error("Set key error : " + e);
		} finally {
			if (null != jedis) {
				jedis.close();
			}
		}
	}
}

  RedisConfig and RedisUtil are both in the common module in the figure above. If the car module needs to use redis, it only needs to rely on common and configure redis in the Configuration file. The Configuration of redis is used immediately, but the cec module also depends on the common module, and it does not need to use redis. Because the @ Configuration annotation is used in RedisConfig, When the cec module relies on common but does not configure redis Configuration, an error will be reported when starting, and the Configuration file cannot be found;

The above requirements cannot be met no matter whether this annotation or other annotation methods are used to read the configuration file;

Analysis: the redis configuration file should be read only when it needs to be used in my business;

Guess: when reading the configuration file traditionally, the ResourceBundle under the java.util package is used to read the configuration file with. properties suffix. Guess whether the. yml configuration file can be read in the same way?

My configuration file path is shown in the figure below (Note: there is an additional config, so a layer needs to be added for code reading):

 

Try:

1. First read the environment configuration spring.profiles.active;

2. Splice the configuration files of the actual environment according to the environment configuration;

/**
     * @Description: Read the corresponding environment configuration file (yml format) according to the environment
     * @Author: Niel
     * @Date: 2021/9/9 8:48 morning
     * @params:
     * @param propertyFileName yml Configuration file name, no suffix required
     * @param propertyName Attribute name
     * @return: java.lang.String
     **/
    public static String getYmlStringForActive(String propertyFileName, String propertyName) {
        YamlPropertiesFactoryBean yamlMapFactoryBean = new YamlPropertiesFactoryBean();
        yamlMapFactoryBean.setResources(new ClassPathResource("config/application.yml"));
        Properties properties = yamlMapFactoryBean.getObject();
        String active = properties.getProperty("spring.profiles.active");
        yamlMapFactoryBean = new YamlPropertiesFactoryBean();
        yamlMapFactoryBean.setResources(new ClassPathResource("config/"+propertyFileName + "-" + active + ".yml"));
        properties = yamlMapFactoryBean.getObject();
        //Get parameters in yml
        String param = properties.getProperty(propertyName);
        if(StringUtil.isBlank(param)){
            return "0";
        }
        return param;
    }

Run the car module to read:

public class AcigaChargeMotorServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AcigaChargeMotorServiceApplication.class, args);
        String redisIp = ResourceBundleUtil.getYmlStringForActive("application","redis.ip");
        System.out.println("==================Test read yml file"+redisIp);
    }
}

result:

  Read the configuration file perfectly and start the cec module again without error, because it does not need to be called in the business, so it will not be read or reported;

If the purpose has been achieved, try to deploy the test environment for verification;

As a result, it is found that the dev development environment configuration is still read;

reason:

nohup java -jar -Dspring.profiles.active=test

The environment specified in the startup script is test, but spring.profiles.active in the actual application configuration file is still dev

When observing the springboot boot boot, one line of output is found:

2021-09-15 15:25:24.530 1 main INFO c.a.c.m.a:655 - The following profiles are active: dev

Guess: dev can be printed here. Can we get this parameter in the same static way?  

  (1) Trace code: SpringApplication.run method

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);  // Print it here and track it in
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

  (2) Trace code: SpringApplication.prepareContext method

private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   applyInitializers(context);
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);  // The name is obvious. Keep tracking
   }
   ......
}

(3) Trace code: SpringApplication.logStartupProfileInfo method

protected void logStartupProfileInfo(ConfigurableApplicationContext context) { 
   Log log = getApplicationLog();
   if (log.isInfoEnabled()) {
      String[] activeProfiles = context.getEnvironment().getActiveProfiles();
      if (ObjectUtils.isEmpty(activeProfiles)) {
         String[] defaultProfiles = context.getEnvironment().getDefaultProfiles();
         log.info("No active profile set, falling back to default profiles: "
               + StringUtils.arrayToCommaDelimitedString(defaultProfiles)); 
      }
      else {
         log.info("The following profiles are active: "
               + StringUtils.arrayToCommaDelimitedString(activeProfiles));  //Yes, obviously, the ApplicationContxt container is used. The next step is to write a tool class to get the Application.
 
     }
   }
}

There happens to be a SpringContextUtil in the tool class:

package com.xx.xx.common.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @ClassName SpringContextUtil
 * @ProjectName aciga-charge
 * Get the container object in spring by the name of the spring bean
 * @author Niel
 * @date 2021/7/19 3:19 afternoon
 * @Version 1.0
 */
@Component
public class SpringContextUtil implements ApplicationContextAware {

	private static ApplicationContext context = null;

	private SpringContextUtil() {
		super();
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}

	/**
	 * Get bean by name
	 * @param beanName
	 * @return
	 */
	public static Object getBean(String beanName) {
		return context.getBean(beanName);
	}

	/**
	 * Gets the bean of the specified type according to the bean name
	 * @param beanName bean name
	 * @param clazz The returned bean type. If the type does not match, an exception will be thrown
	 */
	public static <T> T getBean(String beanName, Class<T> clazz) {
		return context.getBean(beanName, clazz);
	}
	
.......

	///Get current environment
	public static String getActiveProfile() {
		return context.getEnvironment().getActiveProfiles()[0];
	}

}

Overwrite read configuration file method:

/**
     * @Description: Read the corresponding environment configuration file (yml format) according to the environment
     * @Author: Niel
     * @Date: 2021/9/9 8:48 morning
     * @params:
     * @param propertyFileName yml Configuration file name, no suffix required
     * @param propertyName Attribute name
     * @return: java.lang.String
     **/
    public static String getYmlStringForActive(String propertyFileName, String propertyName) {
        YamlPropertiesFactoryBean yamlMapFactoryBean = new YamlPropertiesFactoryBean();
        yamlMapFactoryBean.setResources(new ClassPathResource("config/application.yml"));
        Properties properties = yamlMapFactoryBean.getObject();
        String active = SpringContextUtil.getActiveProfile();//Read current environment
        yamlMapFactoryBean = new YamlPropertiesFactoryBean();
        yamlMapFactoryBean.setResources(new ClassPathResource("config/"+propertyFileName + "-" + active + ".yml"));
        properties = yamlMapFactoryBean.getObject();
        //Get parameters in yml
        String param = properties.getProperty(propertyName);
        if(StringUtil.isBlank(param)){
            return "0";
        }
        return param;
    }

Test again, solve it perfectly, and I won't post the result;

So far, the static method reads the yml configuration file

Posted by Nat on Fri, 17 Sep 2021 13:24:26 -0700