需求点
在Spring Boot下是支持多个配置文件的,只需import即可
spring:
config:
import:
- classpath:/a.yml
- classpath:/b.yml
但是Spring并不会把这些文件里面相同配置项的List合并,而是替换,源码参阅:org.springframework.core.env.MutablePropertySources#addFirst
举个栗子:
a.yml
spring:
cloud:
gateway:
routes:
- id: id1
uri: http://127.0.0.1:8724/admin/categ/test1
predicates:
- Path=/admin/categ/test1
- id: id2
uri: http://127.0.0.1:8724/admin/categ/test2
predicates:
- Path=/admin/categ/test2
b.yml
spring:
cloud:
gateway:
routes:
- id: id3
uri: http://127.0.0.1:8724/admin/categ/test3
predicates:
- Path=/admin/categ/test3
routes里面的配置项默认a.yml 会被 b.yml替换掉,而不是合并
思路
- 不使用自带的import的方式导入配置文件,而是继承
EnvironmentPostProcessor
动态添加配置 - 手动将多个文件的配置项合并成一个
PropertySource
在下面的源码打断点就可以知道,一个文件就是一个PropertySource,而里面就是这样的内容:
"spring.cloud.gateway.routes[0].id" -> {OriginTrackedValue$OriginTrackedCharSequence@2910} "id1"
"spring.cloud.gateway.routes[0].uri" -> {OriginTrackedValue$OriginTrackedCharSequence@2912} "http://127.0.0.1:8724/admin/categ/test1"
"spring.cloud.gateway.routes[0].predicates[0]" -> {OriginTrackedValue$OriginTrackedCharSequence@2914} "Path=/admin/categ/test1"
"spring.cloud.gateway.routes[1].id" -> {OriginTrackedValue$OriginTrackedCharSequence@2916} "id2"
"spring.cloud.gateway.routes[1].uri" -> {OriginTrackedValue$OriginTrackedCharSequence@2918} "http://127.0.0.1:8724/admin/categ/test2"
"spring.cloud.gateway.routes[1].predicates[0]" -> {OriginTrackedValue$OriginTrackedCharSequence@2920} "Path=/admin/categ/test2"
也就是说,每一个配置文件中的配置项都是从0开始, 第一个文件:
spring.cloud.gateway.routes[0].id
spring.cloud.gateway.routes[1].id
第二个文件又是从0开始
spring.cloud.gateway.routes[0].id
合并后应该得这样,才不会被替换掉:
spring.cloud.gateway.routes[0].id
spring.cloud.gateway.routes[1].id
spring.cloud.gateway.routes[2].id
实现
/**
* @author heys1
* @date 2022/5/25
*/
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* 根据正则,获取配置项序号
*
* @see CustomEnvironmentPostProcessor#getPropNum(java.lang.String)
*/
private String REGULAR = "spring.cloud.gateway.routes\[(.+)\]\.(.+)$";
/**
* 存放yaml文件名
*/
private String[] profiles = {
"config/a.yml",
"config/b.yml",
};
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//这里可以取运行环境,自行拓展配置文件的文件名
String[] activeProfiles = environment.getActiveProfiles();
//以配置文件进行分组
List<Map<String, List<Map.Entry<String, OriginTrackedValue>>>> propGroupList = new ArrayList<>();
//遍历profiles
for (String profile : profiles) {
//加载配置文件,
Resource resource = new ClassPathResource(profile);
PropertySource<?> propertySource = loadProfiles(resource);
//记录所有配置项
Map<String, OriginTrackedValue> sourceMap = (Map<String, OriginTrackedValue>) propertySource.getSource();
//根据序号分组
Map<String, List<Map.Entry<String, OriginTrackedValue>>> group = sourceMap
.entrySet()
.stream()
.filter(e -> this.isMatch(e.getKey()))
.collect(Collectors.groupingBy(s -> String.valueOf(getPropNum(s.getKey()))));
propGroupList.add(group);
}
//添加到Spring 配置上下文
this.merge(environment, propGroupList);
}
/**
* 将所有配置文件的配置项合并为一个 PropertySource,并添加到Spring 配置上下文
*
* @param groupList PropertySource 分组
*/
public void merge(ConfigurableEnvironment environment, List<Map<String, List<Map.Entry<String, OriginTrackedValue>>>> groupList) {
Map<String, OriginTrackedValue> routesMap = new TreeMap<>();
int i = 0;
for (Map<String, List<Map.Entry<String, OriginTrackedValue>>> groupItem : groupList) {
for (Map.Entry<String, List<Map.Entry<String, OriginTrackedValue>>> routeItem : groupItem.entrySet()) {
for (Map.Entry<String, OriginTrackedValue> routePropItem : routeItem.getValue()) {
int itemSort = getPropNum(routePropItem.getKey());
String newKey = routePropItem.getKey().replaceFirst(Integer.toString(itemSort), Integer.toString(i));
routesMap.put(newKey, routePropItem.getValue());
}
i++;
}
}
PropertySource<Map<String, OriginTrackedValue>> propertySource =
new PropertySource<Map<String, OriginTrackedValue>>(this.getClass().getName(), routesMap) {
@Override
public OriginTrackedValue getProperty(String s) {
return routesMap.get(s);
}
};
environment.getPropertySources().addFirst(propertySource);
}
/**
* 通过正则获取当前route配置字符串的序号
*
* @param key spring.cloud.gateway.routes[n].xxx
* @return n
*/
private int getPropNum(String key) {
Pattern r = Pattern.compile(REGULAR);
Matcher m = r.matcher(key);
if (m.find()) {
return Integer.parseInt(m.group(1));
}
throw new RuntimeException("配置格式有问题");
}
/**
* 目标配置项是否符合匹配要求
*/
private boolean isMatch(String key) {
try {
getPropNum(key);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取配置项
*
* @param resource
* @return
*/
@SneakyThrows
private PropertySource<?> loadProfiles(Resource resource) {
if (Objects.requireNonNull(resource.getFilename()).contains("yml")) {
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
List<PropertySource<?>> propertySources = sourceLoader.load(resource.getFilename(), resource);
return propertySources.get(0);
}
Properties properties = new Properties();
properties.load(resource.getInputStream());
return new PropertiesPropertySource(resource.getFilename(), properties);
}
@Override
public int getOrder() {
return 0;
}
}
src/main/resources/META-INF/spring.factories
添加配置:
org.springframework.boot.env.EnvironmentPostProcessor={你的项目路径}.CustomEnvironmentPostProcessor