在spring boot项目中,支持两种配置文件的方式。一种为properties,一种为yml。使用yml格式的配置文件,结构更加清晰。
在项目中获取配置文件的值,可以在Bean类中使用@Value注解获取配置项的值。但是如果我们需要在非Bean类(如工具类)静态方法中想使用yml中的配置项时,怎么办呢?下面我们来介绍我们系统级的解决方案:YamlUtils。
YamlUtils功能介绍:可在项目中,任意地方获取yml配置文件中的配置项。支持上下级yml配置项覆盖,支持profile切换,支持变量值嵌套等。已提供以下多种方法:
方法名 | 参数 | 返回类型 | 说明 |
get | key | Object | 获取配置项对应的值 |
getString | key | String | 获取配置项对应的字符串值 |
getInteger | key | Integer | 获取配置项对应的整形值 |
getLong | key | Long | 获取配置项对应的长整形值 |
getBoolean | key | Boolean | 获取配置项对应的布尔值 |
getFloat | key | Float | 获取配置项对应的浮点值 |
getDouble | key | Double | 获取配置项对应的浮点值 |
getMap | key | Map | 获取配置项对应的map值 |
getValueInMap | mapKey,keyInMap | Object | 获取配置项对应的map内key对应的值 |
getList | key | List<Object> | 获取配置项对应的list值 |
getArray | key | String[] | 获取配置项对应的array值 |
- 功能展示:
- 支持profile
2. 支持复杂类型
- 微服务项目结构框图
从结构上可以将我们的框架分为四个层级。
- 业务层;
- 业务依赖层,为业务公共部分。如项目中多个微服务药连接公共的IP地址、eureka地址的维护等。
- 公共依赖层;业务底层公共部分,不同项目也能共用的部分。做认证、鉴权、工具包集合、配置项提取等。
- 基础层。第三方提供的各种基础包。
除基础层,每一层都有对应的配置文件,且配置项随结构定位也有层级依赖,一共3层,上层可覆盖下层配置项。如common-application.yml -> center-common-application.yml -> salary-application.yml。配置项如果存在于common-application.yml,如果不满足需求,需要在center-common-application.yml或者salary-application.yml进行覆盖。
- 实现原理
- 在启动类首行使用SpringUtils.initSpringConfigLocationProperty()初始化需要加载的配置文件;
- YamlUtils类静态代码块中按照一定顺序加载初始化的所有配置文件,将所有配置项加载到内存中,同名配置项会被替换;
- 根据不同的方法、不同的key值获取对应的结果即可。
- 相关代码剖析
SpringUtils
@Slf4j
public class SpringUtils {
/**
* 配置文件列表
*/
@Getter
private static List<String> configurationFileList;
/**
* 设置app.name配置
*
* @return
*/
public static void setAPPNameProperty(String appName) {
System.setProperty("app.name", appName);
System.setProperty("spring.application.name", appName);
}
/**
* 初始化基础配置文件路径
*
* @return
*/
public static void initSpringConfigLocationProperty() {
initSpringConfigLocationProperty(null);
}
/**
* 初始化基础配置文件路径
*
* @param configFileLocations 配置文件位置集合
* @return
*/
public static void initSpringConfigLocationProperty(String... configFileLocations) {
List<String> configOriginList = new ArrayList<>();
configOriginList.addAll(Arrays.asList(Constants.SPRING_CONFIG_LOCATION_PROPERTY_BOOT_BASE));
if (configFileLocations != null) {
configOriginList.addAll(Arrays.asList(configFileLocations));
}
configOriginList.addAll(Arrays.asList(Constants.SPRING_CONFIG_LOCATION_PROPERTY));
//再次调整顺序,将所有的config文件夹提前
List<String> configurationList = configOriginList.stream().filter(
configuration -> StringUtils.isNotEmpty(configuration)).collect(Collectors.toList());
log.debug("configurationList: [{}]", configurationList);
List<String> classpathConfigurationList = configurationList.stream()
.filter(configuration -> configuration.startsWith("classpath")).collect(Collectors.toList());
List<String> localConfigurationList = configurationList.stream()
.filter(configuration -> configuration.startsWith("file:")).collect(Collectors.toList());
ArrayList<String> resultList = new ArrayList<>();
resultList.addAll(classpathConfigurationList);
resultList.addAll(localConfigurationList);
configurationFileList = resultList;
log.trace("configurationFileList: [{}]", configurationFileList);
String newConfigurationsString = resultList.stream().collect(Collectors.joining(","));
log.info("spring.config.location: [{}]", newConfigurationsString);
System.setProperty("spring.config.location", newConfigurationsString);
}
}
Constants
/**
* spring boot基础配置文件,application.properties添加在此会影响应用从外部加载配置文件
*/
public static final String[] SPRING_CONFIG_LOCATION_PROPERTY_BOOT_BASE = new String[]{"classpath:/config/application-common.yml"};
/**
* spring 基础配置文件,application.properties添加在此会影响应用从外部加载配置文件
*/
public static final String[] SPRING_CONFIG_LOCATION_PROPERTY = new String[]{"classpath:/config/application.yml", "file:config/application.yml"};
YamlUtils
/**
* yml配置工具类
*/
@Slf4j
public class YamlUtils {
/**
* 配置表
*/
private static Map<String, Object> sourceMap = new HashMap<>();
private static final String HEAD = "${";
private static final String TAIL = "}";
static {
loadAll();
checkRootParentPath();
}
/**
* 加载所有配置文件
*/
public static void loadAll() {
List<String> configurationFileList = SpringUtils.getConfigurationFileList();
if (configurationFileList == null) {
throw new IllegalStateException("configurationFileList未成功获取");
}
//查找激活的profile
List<Object> activeProfileNameList = new ArrayList<>();
String activeProfileKey = "spring.profiles.active";
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
for (int i = configurationFileList.size() - 1; i >= 0; i--) {
String configurationFile = configurationFileList.get(i);
Resource resource = defaultResourceLoader.getResource(configurationFile);
try {
load(configurationFile, resource, null);
//配置的单profile
if (get(activeProfileKey) != null) {
String activeProfileName = get(activeProfileKey) + "";
activeProfileNameList.add(activeProfileName);
sourceMap.clear();
break;
} else if (getList(activeProfileKey) != null && getList(activeProfileKey).size() > 0) {
activeProfileNameList = getList(activeProfileKey);
sourceMap.clear();
break;
}
} catch (Exception e) {
log.trace("加载配置文件失败: [{}]", configurationFile, e);
}
}
log.info("activeProfileNameList: {}", activeProfileNameList);
//读取配置
for (int i = 0; i < configurationFileList.size(); i++) {
String configurationFile = configurationFileList.get(i);
Resource resource = defaultResourceLoader.getResource(configurationFile);
log.debug("加载配置文件: [{}]", configurationFile);
load(configurationFile, resource, null);
for (Object activeProfileName : activeProfileNameList) {
load(configurationFile, resource, activeProfileName + "");
}
}
}
/**
* 加载配置文件
*
* @param name
* @param resource
* @param profile
*/
private static void load(String name, Resource resource, String profile) {
try {
MapPropertySource mapPropertySource = (MapPropertySource) new YamlPropertySourceLoader()
.load(name, resource, profile);
if (mapPropertySource == null) {
return;
}
Map<String, Object> sourceOne = mapPropertySource.getSource();
log.trace("name: [{}], source: [{}], profile: [{}]", name, sourceOne, profile);
sourceMap.putAll(sourceOne);
log.trace("sourceMap after marge: [{}]", sourceMap);
} catch (Exception e) {
log.trace("加载配置文件失败: [{}]", name, e);
}
}
/**
* 获取对象
*
* @param key
* @return
*/
public static Object get(String key) {
Object object = sourceMap.get(key);
if (object == null) {
return null;
}
//解析内部变量
return parseVariable(object);
}
/**
* 获取对象
*
* @param key
* @return
*/
public static String getString(String key) {
return get(key) + "";
}
/**
* 获取对象
*
* @param key
* @return
*/
public static int getInteger(String key) {
return Integer.parseInt(getString(key));
}
/**
* 获取对象
*
* @param key
* @return
*/
public static long getLong(String key) {
return Long.parseLong(getString(key));
}
/**
* 获取对象
*
* @param key
* @return
*/
public static boolean getBoolean(String key) {
return Boolean.parseBoolean(getString(key));
}
/**
* 获取对象
*
* @param key
* @return
*/
public static float getFloat(String key) {
return Float.parseFloat(getString(key));
}
/**
* 获取对象
*
* @param key
* @return
*/
public static double getDouble(String key) {
return Double.parseDouble(getString(key));
}
/**
* 获取对象
*
* @param key
* @return
*/
public static Map<String, Object> getMap(String key) {
Set<String> keySet = sourceMap.keySet();
List<String> matchedKeyList = keySet.stream()
.filter(tmpKey -> {
if (tmpKey.startsWith(key) && !tmpKey.equals(key)) {
String tail = tmpKey.substring(key.length() + 1);
if (tail.indexOf("\\.") == -1) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
Map<String, Object> map = new HashMap<>();
for (String matchKey : matchedKeyList) {
String retKey = matchKey.substring(key.length() + 1).toLowerCase();
map.put(retKey, parseVariable(sourceMap.get(matchKey)));
}
return map;
}
/**
* 获取对象
*
* @param mapKey
* @param keyInMap
* @return
*/
public static Object getValueInMap(String mapKey, String keyInMap) {
Map<String, Object> map = getMap(mapKey);
if (map == null) {
throw new IllegalStateException("can not found map with key: " + mapKey);
}
return map.get(keyInMap.toLowerCase());
}
/**
* 获取对象
*
* @param key
* @return
*/
public static List<Object> getList(String key) {
Set<String> keySet = sourceMap.keySet();
List<String> matchedKeyList = keySet.stream()
.filter(tmpKey -> {
if (tmpKey.startsWith(key)) {
String tail = tmpKey.substring(key.length());
if (tail.matches("\\[\\d+\\]")) {
return true;
}
}
return false;
})
.collect(Collectors.toList());
List<Object> objectList = new ArrayList<>();
for (String matchKey : matchedKeyList) {
objectList.add(sourceMap.get(matchKey));
}
return objectList;
}
/**
* 获取对象
*
* @param key
* @return
*/
public static String[] getArray(String key) {
return getString(key).split(",");
}
/**
* 解析变量
*
* @param object
* @return
*/
private static Object parseVariable(Object object) {
String objectString = object.toString();
int indexHead = objectString.indexOf(HEAD);
while (indexHead != -1) {
int indexTail = objectString.indexOf(TAIL, indexHead);
String variableName = objectString.substring(indexHead + HEAD.length(), indexTail);
Object variableValue = sourceMap.get(variableName);
objectString = objectString.substring(0, indexHead) + variableValue + objectString.substring(indexTail + TAIL.length());
indexHead = objectString.indexOf(HEAD);
}
return objectString;
}
/**
* 检查系统根路径是否是windows格式,是则报错
*
* @return
*/
public static void checkRootParentPath() {
String rootParentPath = getString("dwbi.base-common.root-parent-path");
if (!rootParentPath.startsWith("/")) {
throw new IllegalStateException("rootParentPath should be matches unix path. rootParentPath: " + rootParentPath);
}
}
}