关于SpringBoot加载配置文件中Map的顺序问题

本文介绍了如何在Spring配置文件中按顺序读取和配置Map类型参数。从简单的YAML配置到使用额外的配置文件,再到自定义PropertySourceFactory和YamlPropertiesFactoryBean以保持配置顺序,详细阐述了解决方案。通过定制LinkedHashMapProperties确保配置的顺序性。
摘要由CSDN通过智能技术生成

起因

  我这的需求呢很简单,就是在配置文件中配置一个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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JustDoSelf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值