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配置文件搞定