Spring Boot加载json配置文件

6,284 阅读3分钟

熟悉Spring Boot的小伙伴想必都知道Spring Boot可以加载properties和yaml格式的配置文件,Spring Boot启动时会自动加载并解析其中的内容。

Properties配置文件简单示例

server.port=8080

Yaml配置文件简单示例

server:
  port: 8080

但假设要加载json格式的配置文件?例如这样

{
  "server": {
    "port": 8080
  }
}

这里介绍几个方法,测试的版本是Spring Boot 2.2.4,json解析框架是Fastjson。

方法1

类似于application.propertiesapplication.yaml,该方法可以自动加载并解析application.json配置文件。

主要利用了Spring Boot的PropertySourceLoader接口和SPI机制,以下是接口定义

public interface PropertySourceLoader {

	/**
	 * 返回支持的文件扩展名(不包括.)
	 * @return 文件扩展名
	 */
	String[] getFileExtensions();

	/**
	 * 从资源中加载一个或者多个属性源
	 * @param name 每个属性源的前缀名
	 * @param resource 要加载的资源
	 * @return 属性源数组
	 * @throws IOException if the source cannot be loaded
	 */
	List<PropertySource<?>> load(String name, Resource resource) throws IOException;

}

我们可以看到已经官方已经有两个实现类:PropertiesPropertySourceLoaderYamlPropertySourceLoader,分别对应着properties配置文件加载类和yaml文件格式加载类。类似地,我们实现这两个方法。

public class JsonPropertySourceLoader implements PropertySourceLoader {

    @Override
    public String[] getFileExtensions() {
        return new String[]{"json"};
    }
    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        //从resource即application.json加载字符串
        String jsonStr = readResourceAsString(resource);
        //解析字符串为JSONObject
        JSONObject jsonObject = (JSONObject) JSON.parse(jsonStr);
        //map用于存放属性
        Map<String,Object> map = new HashMap<>();
        jsonObject.forEach((key, value) -> putEntry(map, key, value));
        if (map.isEmpty()) {
            return Collections.emptyList();
        }

        return Collections
                .singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(map), true));
    }


    /***
     * 针对多级的结构
     */
    private void putEntry(Map<String, Object> map, String key, Object value) {
        if (value instanceof JSONObject){
            ((JSONObject) value).forEach((key1, value1) -> putEntry(map, key + "." + key1, value1));
        }else if (value instanceof JSONArray){
            map.put(key, convertArray((JSONArray)value));
        } else{
            map.put(key,value);
        }
    }

    /**
     * 你可以根据自身需要定义数组如何解析
     * 将json数组转换为字符串 格式如下: 1,2,3
     * @param array
     * @return
     */
    private String convertArray(JSONArray array) {
        if (array.isEmpty()){
            return "";
        }
        StringBuilder builder = new StringBuilder();
        array.forEach(value -> {
            builder.append(",");
            if (value instanceof JSONObject || value instanceof JSONArray){
                builder.append(JSON.toJSON(value));
            }else {
                builder.append(value);
            }
        });
        return builder.substring(1);
    }

    /**
     * 将资源加载为一个字符串
     */
    private  String readResourceAsString(Resource resource) throws IOException {
        StringBuilder builder = new StringBuilder();
        InputStream in = resource.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(in);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String str ;
        while ((str = bufferedReader.readLine())!=null){
            builder.append(str);
        }
        //关闭流
        bufferedReader.close();
        inputStreamReader.close();
        in.close();
        return builder.toString();
    }
}

然后利用Spring Boot的类SPI扩展机制,在META-INF/spring.factories加入

# 请替换等号后的内容为你自己项目中JsonPropertySourceLoader的全限定名
org.springframework.boot.env.PropertySourceLoader=com.example.demo.JsonPropertySourceLoader

方法2

方法1支持application.json配置文件的加载,但是如果想要加载其他文件名的文件(例如config.json),怎么办?

这里利用Spring Boot的动态管理配置文件扩展接口EnvironmentPostProcessor,为了文章的简洁,这里复用了方法1的JsonPropertySourceLoader的加载配置方法,但实际可以不要这个类。

public class JsonEnvironmentPostProcessor implements EnvironmentPostProcessor {

    //存放自定义加载的配置文件列表,请根据自己需要修改
    private String[] profiles= new String[]{"config.json"};

  	// 实际加载类
    private PropertySourceLoader loader = new JsonPropertySourceLoader();

  	// 向environment注入配置
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        for (String profile : profiles){
            Resource resource = new ClassPathResource(profile);
           try {
                PropertySource<?>  propertySource = loader.load(profile, resource).get(0);
                environment.getPropertySources().addLast(propertySource);
            } catch (IOException e) {
                // do nothing
            }
        }
    }
}

META-INF/spring.factories加入

# 请替换等号后的内容为你自己项目中JsonEnvironmentPostProcessor的全限定名
org.springframework.boot.env.EnvironmentPostProcessor=com.example.demo.JsonEnvironmentPostProcessor

注意的是,EnvironmentPostProcessor的加载顺序在PropertySourceLoader之前。

方法3

该方法可以支持@PropertySource注解,加载自定义配置文件。出于简洁,依然复用之前JsonPropertySourceLoader的方法。

实现如下

public class JsonPropertySourceFactory implements PropertySourceFactory {

 
    private PropertySourceLoader loader = new JsonPropertySourceLoader();

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        return loader.load(name, resource.getResource()).get(0);
    }
}

使用示例如下

@PropertySource(value = "myconfig.json",factory = JsonPropertySourceFactory.class)

除上述的方法外,还可以通过重写PropertiesLoaderSupportloadProperties方法,需要注意加载顺序。