Java程式碼修改yml配置檔案屬性

冰川_發表於2020-11-16

參考部落格

問題背景

開發agent需要接受從服務端回傳的Agent資訊以及心跳回傳週期,
客戶端不能安裝資料庫,考慮存放在yml配置檔案中通過程式碼動態修改引數。

需要匯入的依賴

<dependency>
	<groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
</dependency>

修改yml的工具類

package agent.utils;

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author yichuan@iscas.ac.cn
 * 修改YmL檔案的工具類
 * @version 1.0
 * @date 2020/11/14 17:01
 */
public class YmlUtil {
    private final static DumperOptions OPTIONS = new DumperOptions();

    private static File file;

    private static InputStream ymlInputSteam;

    private static Object CONFIG_MAP;

    private static Yaml yaml;

    static {
        //將預設讀取的方式設定為塊狀讀取
        OPTIONS.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
    }

    /**
     * 使用其他方法之前必須呼叫一次 設定yml的輸出檔案,當沒有設定輸入流時可以不設定輸入流,預設以此檔案讀入
     *
     * @param file 輸出的檔案
     */
    public static void setYmlFile(File file) throws FileNotFoundException {
        YmlUtil.file = file;
        if (ymlInputSteam == null) {
            setYmlInputSteam(new FileInputStream(file));
        }
    }


    /**
     * 使用其他方法之前必須呼叫一次 設定yml的輸入流
     *
     * @param inputSteam 輸入流
     */
    public static void setYmlInputSteam(InputStream inputSteam) {
        ymlInputSteam = inputSteam;
        yaml = new Yaml(OPTIONS);
        CONFIG_MAP = yaml.load(ymlInputSteam);
    }

    /**
     * 根據鍵獲取值
     *
     * @param key 鍵
     * @return 查詢到的值
     */
    @SuppressWarnings("unchecked")
    public static Object getByKey(String key) {
        if (ymlInputSteam == null) {
            return null;
        }
        String[] keys = key.split("\\.");
        Object configMap = CONFIG_MAP;
        for (String s : keys) {
            if (configMap instanceof Map) {
                configMap = ((Map<String, Object>) configMap).get(s);
            } else {
                break;
            }
        }
        return configMap == null ? "" : configMap;
    }

    public static void saveOrUpdateByKey(String key, Object value) throws IOException {
        KeyAndMap keyAndMap = new KeyAndMap(key).invoke();
        key = keyAndMap.getKey();
        Map<String, Object> map = keyAndMap.getMap();
        map.put(key, value);
        //將資料重新寫回檔案
        yaml.dump(CONFIG_MAP, new FileWriter(file));
    }

    public static void removeByKey(String key) throws Exception {
        KeyAndMap keyAndMap = new KeyAndMap(key).invoke();
        key = keyAndMap.getKey();
        Map<String, Object> map = keyAndMap.getMap();
        Map<String, Object> fatherMap = keyAndMap.getFatherMap();
        map.remove(key);
        if (map.size() == 0) {
            Set<Map.Entry<String, Object>> entries = fatherMap.entrySet();
            for (Map.Entry<String, Object> entry : entries) {
                if (entry.getValue() == map) {
                    fatherMap.remove(entry.getKey());
                }
            }
        }
        yaml.dump(CONFIG_MAP, new FileWriter(file));
    }

    private static class KeyAndMap {
        private String key;
        private Map<String, Object> map;
        private Map<String, Object> fatherMap;

        public KeyAndMap(String key) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }

        public Map<String, Object> getMap() {
            return map;
        }

        public Map<String, Object> getFatherMap() {
            return fatherMap;
        }

        @SuppressWarnings("unchecked")
        public KeyAndMap invoke() {
            if (file == null) {
                System.err.println("請設定檔案路徑");
            }
            if (null == CONFIG_MAP) {
                CONFIG_MAP = new LinkedHashMap<>();
            }
            String[] keys = key.split("\\.");
            key = keys[keys.length - 1];
            map = (Map<String, Object>) CONFIG_MAP;
            for (int i = 0; i < keys.length - 1; i++) {
                String s = keys[i];
                if (map.get(s) == null || !(map.get(s) instanceof Map)) {
                    map.put(s, new HashMap<>(4));
                }
                fatherMap = map;
                map = (Map<String, Object>) map.get(s);
            }
            return this;
        }
    }
}

測試類

import java.io.File;
import java.util.Objects;
/**
 * @author yichuan@iscas.ac.cn
 * yml編輯工具測試類
 * @version 1.0
 * @date 2020/11/14 17:11
 */
public class test {
    public static void main(String[] args) throws Exception {
        /**
         * 這裡修改的是target目錄編譯後的路徑,所以執行除錯時。src目錄下不會變
         */
        File yml = new File(Objects.requireNonNull(test.class.getClassLoader().getResource("application.yml")).toURI());
        //不管執行什麼操作一定要先執行這個
        YmlUtil.setYmlFile(yml);
        System.out.println(YmlUtil.getByKey("修改前"+"heart.agentId"));
        System.out.println("aaaaaa");
        YmlUtil.saveOrUpdateByKey("heart.agentId", "哈哈哈哈");
        //YmlUtil.removeByKey("heart.agentId");
    }
}

這裡要注意一個問題,修改的是target裡面的配置檔案,而不是src/resources裡面的

存在的後續問題

專案是打成jar包執行,執行過程中會遇到java.lang.IllegalArgumentException: URI is not hierarchical的報錯。
程式碼中使用的是File f = new File(this.getClass().getResource("路徑/目錄").toURI());讀取該路徑下所有檔案,本來在程式碼環境下執行是正常的,可是後來打包後,在執行出現URI is not hierarchical 錯誤。
經過DEBUG發現,原來本地時,讀取檔案時,URI路徑是:file:/E:/idea-workspace/project/module(jar包所在的module)/target/classes/package/路徑或者目錄,但是打包之後,同樣是讀取該路徑下的所有檔案,但是URI卻變成了:jar:file:/E:/idea-workspace/project/module(war打包的模組)/target/war包名稱/WEB-INF/lib/需要讀取的包名稱.jar!/路徑或者是目錄,從jar所在模組讀取檔案變成了從war中的lib下的打包好的jar中讀取class檔案,報錯URI is not hierarchical 錯誤。

解決方法,將yml配置檔案移到jar包外面

讀取時直接寫檔名即可

File yml = new File("application.yml");

獲取引數時需要每次都重新整理,採用讀取檔案,而不是注入後採用get

YmlUtil.setYmlFile(yml);
YmlUtil.getByKey();

修改執行時的引數

java -jar demo.jar --spring.config.location=路徑(application.yml)

相關文章