起因
我这的需求呢很简单,就是在配置文件中配置一个map类型的参数,然后读取的时候按配置的顺序读取出来,本来就是很容易的一个东西,结果误入歧途,翻看了一些源码,最终复杂版的还是解决了这个问题。
简易版
其实这个很简单,直接在application.yml默认配置文件中配置
test:
filter:
"keyA": "valueA"
"keyB": "valueB"
"keyC": "valueC"
或者
test:
filter: {"keyA": "valueA","keyB": "valueB","keyC": "valueC"}
格式的就ok了,简单粗暴。
进阶版
如果默认配置文件中的配置项过多,再加上这些配置也是过多,那可以将这些放到一个新的配置文件中。但是配置文件起名要求已application开头,例如:application-test.yml
;然后在application.yml
文件中加入
spring:
profiles:
include: test #如果有多个可以用逗号分开
然后新的配置文件也就能配加载进来,并且读取的时候也会按配置的顺序读取出来。
复杂版
上面两种固然能解决这个问题,但是…谁让我刚开始就走了这条不归路呢;感兴趣的可以看一下,这儿我就直接贴代码和加载方式了。
@Data
@PropertySource(value = {"file:./test.yml"}, factory = YamlPropertySourceFactory.class)
@ConfigurationProperties(prefix = "test")
public TestConfig{
private Map<String, String> filter = new LinkedHashMap<>();
}
通过PropertySource
注解读取指定配置文件,factory
指定配置文件加载的工厂;如果factory
不指定则使用的是默认的工厂,然而这个map里是没有值的,也就是没读到,所以需要自定义一个工厂去加载配置文件。
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
/**
* 导入配置文件方法
* @param resource
* @return
* @throws IOException
*/
private Properties loadYamlIntoProperties(EncodedResource resource) throws IOException {
try {
MyYamlPropertiesFactoryBean factory = new MyYamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException) {
throw (FileNotFoundException) e.getCause();
}
throw e;
}
}
}
这里说明一下,为什么要重新YamlPropertiesFactoryBean.createProperties()
这个方法呢,如果对读取出来的顺序没有要求或者只是普通的配置项,这个就不需要重写了,因为他读进去的顺序是有问题的。
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.reader.UnicodeReader;
import java.io.IOException;
import java.io.Reader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
public class MyYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean {
@Nullable
private Properties properties;
private Resource[] resources = new Resource[0];
@Override
public void setResources(Resource... resources) {
this.resources = resources;
}
@Override
@Nullable
public Properties getObject() {
return (this.properties != null ? this.properties : createProperties());
}
@Override
protected Properties createProperties() {
Yaml yaml = createYaml();
LinkedHashMapProperties result = new LinkedHashMapProperties();
for (Resource resource : this.resources) {
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
for (Object object : yaml.loadAll(reader)) {
if (object != null) {
properties = new LinkedHashMapProperties();
Map<String, Object> flattenedMap = getFlattenedMap(asMap(object));
for (String key : flattenedMap.keySet()) {
properties.put(key, flattenedMap.get(key));
}
}
}
}catch (IOException ex) {
ex.getMessage();
}
}
for (Object key : properties.keySet()) {
result.put(key, properties.getProperty((String) key));
}
return result;
}
private Map<String, Object> asMap(Object object) {
// YAML can have numbers as keys
Map<String, Object> result = new LinkedHashMap<>();
if (!(object instanceof Map)) {
// A document can be a text literal
result.put("document", object);
return result;
}
Map<Object, Object> map = (Map<Object, Object>) object;
map.forEach((key, value) -> {
if (value instanceof Map) {
value = asMap(value);
}
if (key instanceof CharSequence) {
result.put(key.toString(), value);
}
else {
// It has to be a map key in this case
result.put("[" + key.toString() + "]", value);
}
});
return result;
}
}
其实这个LinkedHashMapProperties
类才是重点,因为源码中用的是Properties
,他存进去就没有顺序了,所以继承后重写一下put和keySet方法就好了,然后就是上面在源码里用到Properties
的地方替换成LinkedHashMapProperties
就解决了。
import java.util.*;
public class LinkedHashMapProperties extends Properties {
private final LinkedHashSet<Object> keys = new LinkedHashSet<>();
@Override
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
@Override
public Set<Object> keySet() {
return keys;
}
}