【springboot】静态方法读取yml配置文件,可根据不同环境动态读取

本文介绍了在SpringBoot项目中,如何解决因多个模块共用同一配置导致的启动问题。通过创建一个工具类,动态读取当前环境对应的.yml配置文件,实现了在不同环境下正确加载配置,同时避免了未使用配置模块的启动错误。通过SpringContextUtil获取当前环境,再结合YamlPropertiesFactoryBean加载特定环境的配置文件,成功实现了静态方法读取配置。
摘要由CSDN通过智能技术生成

springboot中读取配置文件的方式有很多种,我这里就不在叙述了;

背景:项目的common中有一个redis的工具类,因为很多模块用,不想在每个模块中都写一个redisconfig,并且使用的是工具类,静态加载的方式使用,但是如果用传统的读取方式如下:

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服务器IP
	public static String IP;

	。。。。。。

	public String getIP() {
		return IP;
	}

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

RedisUtil:

/**
 * Redis 工具类
 */

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);
	// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
	private static boolean TEST_ON_BORROW = false;

	private static volatile JedisPool jedisPool = null;

	/**
	 * redis过期时间,以秒为单位
	 */
	public final static int EXRP_HOUR = 60 * 60; // 一小时
	public final static int EXRP_DAY = 60 * 60 * 24; // 一天
	public final static int EXRP_MONTH = 60 * 60 * 24 * 30; // 一个月

	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);
		}
	}

	/**
	 * 初始化Redis连接池
	 */
	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);
			}
		}
	}

	/**
	 * 在多线程环境同步初始化
	 */
	private static synchronized void poolInit() {
		if (null == jedisPool) {
			initialPool();
		}
	}

	/**
	 * 同步获取Jedis实例
	 *
	 * @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);
				}
				// 获取jedis的时候设置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;
	}

	/**
	 * 设置 String
	 *
	 * @param key
	 * @param value
	 * @param dbSelect
	 *            (设置DB库,传null默认为db0)
	 */
	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和RedisUtil均在上图的common模块,假如car模块需要用到redis只需要依赖一下common并在配置文件中配置上redis的配置即刻使用,但是cec模块同样依赖了common模块,并且它不需要使用redis,由于RedisConfig中使用了@Configuration注解,当cec模块依赖了common但是未配置redis的配置,启动时就会报错找不到配置文件;

不管用不用这个注解或者其他的注解方式读取配置文件,均不能满足上述需求;

分析:这个redis的配置文件,应该是在我业务中需要使用时才去读取;

猜想:传统读取配置文件时使用到了java.util包下面的ResourceBundle进行读取过.properties后缀的配置文件,猜想是否能用同样的方式来读取.yml配置文件呢?

我的配置文件路径如下图(注:多了一个config,故代码读取也需要加一层):

 

尝试:

1.先读取环境配置spring.profiles.active;

2.根据环境配置拼接实际环境的配置文件;

/**
     * @Description: 根据环境区分,读取对应环境配置文件配置(yml格式)
     * @Author: Niel
     * @Date: 2021/9/9 8:48 上午
     * @params:
     * @param propertyFileName yml配置文件名,不需要后缀
     * @param propertyName 属性名称
     * @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();
        //获取yml里的参数
        String param = properties.getProperty(propertyName);
        if(StringUtil.isBlank(param)){
            return "0";
        }
        return param;
    }

运行car模块进行读取:

public class AcigaChargeMotorServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AcigaChargeMotorServiceApplication.class, args);
        String redisIp = ResourceBundleUtil.getYmlStringForActive("application","redis.ip");
        System.out.println("==================测试读取yml文件"+redisIp);
    }
}

结果:

 完美读取到配置文件,再次启动cec模块也不会报错,因为业务中不需要调用它,自然不会进行读取,也不会报错;

已达到目的,尝试部署测试环境,进行验证;

结果悲剧,发现仍然读取的还是dev的开发环境配置;

原因:

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

启动脚本中指定的环境为test,但是实际application配置文件中的spring.profiles.active还是dev

观察springboot启动时发现有一行输出:

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

猜想:这里能打印出dev来,我们是否能同样使用静态的方式得到这个参数? 

 (1)跟踪代码:SpringApplication.run方法

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);  // 在这里打印了,跟踪进去
            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)跟踪代码:SpringApplication.prepareContext方法

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);  // 名称很明显,继续跟踪进去
   }
   ......
}

(3)跟踪代码:SpringApplication.logStartupProfileInfo方法

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));  //找到了,很明显用了ApplicationContxt容器,接下来就是写个工具类来获取Application就行啦。
 
     }
   }
}

正好工具类中有一个SpringContextUtil:

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
 * 通过spring的bean的名称,获取spring中的容器对象
 * @author Niel
 * @date 2021/7/19 3:19 下午
 * @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;
	}

	/**
	 * 根据名称获取bean
	 * @param beanName
	 * @return
	 */
	public static Object getBean(String beanName) {
		return context.getBean(beanName);
	}

	/**
	 * 根据bean名称获取指定类型bean
	 * @param beanName bean名称
	 * @param clazz 返回的bean类型,若类型不匹配,将抛出异常
	 */
	public static <T> T getBean(String beanName, Class<T> clazz) {
		return context.getBean(beanName, clazz);
	}
	
.......

	/// 获取当前环境
	public static String getActiveProfile() {
		return context.getEnvironment().getActiveProfiles()[0];
	}

}

改写读取配置文件方法:

/**
     * @Description: 根据环境区分,读取对应环境配置文件配置(yml格式)
     * @Author: Niel
     * @Date: 2021/9/9 8:48 上午
     * @params:
     * @param propertyFileName yml配置文件名,不需要后缀
     * @param propertyName 属性名称
     * @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();//读取当前环境
        yamlMapFactoryBean = new YamlPropertiesFactoryBean();
        yamlMapFactoryBean.setResources(new ClassPathResource("config/"+propertyFileName + "-" + active + ".yml"));
        properties = yamlMapFactoryBean.getObject();
        //获取yml里的参数
        String param = properties.getProperty(propertyName);
        if(StringUtil.isBlank(param)){
            return "0";
        }
        return param;
    }

再次测试,完美解决,结果我就不贴出来了;

至此静态方法读取yml配置文件搞定

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值