免费爱碰视频在线观看,九九精品国产屋,欧美亚洲尤物久久精品,1024在线观看视频亚洲

      Java 通用配置版本配置實(shí)現(xiàn)

      Java 通用配置版本配置實(shí)現(xiàn)

      Java 通用配置

      (一)設(shè)計(jì)

      (二)JVM和環(huán)境變量實(shí)現(xiàn)

      (三)用戶(hù)配置實(shí)現(xiàn)

      (四)版本配置實(shí)現(xiàn)

      本系列參考實(shí)現(xiàn):

      https://gitee.com/mybatis-mapper/config

      https://github.com/mybatis-mapper/config

      版本配置設(shè)計(jì)

      版本配置是提供給 模塊開(kāi)發(fā)者 使用的, 模塊的使用者 可以使用默認(rèn)的最新版本配置或者通過(guò)參數(shù)指定要使用的版本配置。

      版本配置文件規(guī)范

      模塊開(kāi)發(fā)者一般會(huì)把配置和代碼一起打包,配置文件通常都在 jar 包內(nèi),所以需要從 jar 包讀取資源,在讀取資源時(shí)有很多情況需要考慮,如果允許配置文件出現(xiàn)在任意的 jar 包中,就需要對(duì)所有 jar 包中的資源進(jìn)行掃描,就和 Spring 中配置的掃描包一樣,指定范圍越小,掃描處理越快。

      綜合考慮性能和功能,這里的版本配置中指定了如下的要求:

    1. 配置文件和版本配置子類(lèi):在同一個(gè) jar 包文件中在同一個(gè)包名中
    2. 版本配置文件使用相同的前綴,后面帶上版本號(hào),版本號(hào)為兩位,示例如下:mybatis-mapper-v1.0.propertiesmybatis-mapper-v1.1.propertiesmybatis-mapper-v2.0.propertiesmybatis-mapper-v2.5.properties
    3. 給自己用的工具能實(shí)現(xiàn)需求即可,不用為了靈活性搞得太復(fù)雜。

      版本配置選擇邏輯

      有了上述規(guī)則后,就需要確定該使用哪個(gè)版本的配置文件。

      假設(shè)項(xiàng)目剛開(kāi)始 1.0 版本,此時(shí)組件有一些默認(rèn)值設(shè)置。后期增加了新的配置,可以直接在 1.0 版本中新增,也可以創(chuàng)建一個(gè)和代碼版本號(hào)對(duì)應(yīng)的新配置文件進(jìn)行維護(hù)。前一種方式操作簡(jiǎn)單,后一種方式的配置也是文檔,可以清晰地展示出當(dāng)前版本可以配置的參數(shù)。

      當(dāng)項(xiàng)目有不兼容的版本改動(dòng)時(shí),一定要?jiǎng)?chuàng)建新的配置文件,假設(shè)發(fā)展到2.5版本時(shí),存在下面幾個(gè)版本配置:

    4. mybatis-mapper-v1.0.properties
    5. mybatis-mapper-v1.1.properties
    6. mybatis-mapper-v2.0.properties
    7. mybatis-mapper-v2.5.properties
    8. 某個(gè)用戶(hù)從 1.0 升級(jí)到 2.5 時(shí),發(fā)現(xiàn)有個(gè)默認(rèn) true 的配置變成了 false ,此時(shí)如果組件支持 用戶(hù)配置 ,可以直接在里面配置位置 true ,如果有版本配置,就可以指定要使用的版本配置,此時(shí)指定 mapper.version=v1.0 ,就會(huì)讓 1.0 的配置優(yōu)先級(jí)更高,如果想用 2.0 以前的配置,配置為 mapper.version=v1.9 時(shí)會(huì)使用 1.1 的配置,當(dāng)指定的版本號(hào)在兩個(gè)版本區(qū)間時(shí),向下選擇低版本。

      選擇之外的其他版本,按照版本從高到低的優(yōu)先級(jí)進(jìn)行獲取,這種方式可以保證新版本的代碼在運(yùn)行時(shí)不至于找不到新的配置參數(shù)。

      代碼實(shí)現(xiàn)

      當(dāng)前版本配置的抽象類(lèi)中,提供了下面兩個(gè)抽象方法

      /** * 獲取配置文件名前綴 */protected abstract String getConfigName();/** * 獲取版本號(hào)對(duì)應(yīng)的 key */protected abstract String getVersionKey();

      人類(lèi)只需要實(shí)現(xiàn)這兩個(gè)方法:

      • 第一個(gè)方法是配置文件的前綴,例如 mybatis-mapper , mybatis-provider 。
      • 第二個(gè)方法是返回的配置名,用戶(hù)可以通過(guò)這個(gè)配置名指定自己想要使用的版本,配置名示例如: mapper.version , provider.version ,用戶(hù)可以在前面介紹的 JVM、環(huán)境變量、用戶(hù)配置文件中指定這里的版本號(hào),例如 mapper.version=1.0 , provider.version=v1.1 。

      子類(lèi)本身很好實(shí)現(xiàn),除此之外還需要在子類(lèi)相同的包名下面提供對(duì)應(yīng)的版本配置文件,例如:

      mybatis-mapper-v1.0.propertiesmybatis-mapper-v1.1.propertiesmybatis-mapper-v1.2.propertiesmybatis-mapper-v2.0.properties

      為了方便比較版本號(hào),提供了一個(gè) ConfigVersion 內(nèi)部類(lèi):

      public static class ConfigVersion implements Comparable { private final int x; private final int y; private final String fileName; public ConfigVersion(String version) { this(version, null); } public ConfigVersion(String version, String fileName) { this.fileName = fileName; if (version.startsWith(“v”)) { version = version.substring(1); } String[] strings = version.split(“.”); this.x = Integer.parseInt(strings[0]); this.y = Integer.parseInt(strings[1]); } public String getFileName() { return fileName; } @Override public int compareTo(ConfigVersion o) { if (this.x == o.x) { return this.y – o.y; } return this.x – o.x; }}

      這里的版本規(guī)則就是這樣的 可以 v 開(kāi)頭,版本號(hào)為兩位,合法的版本號(hào)如: v1.0 , 2.0 , 2.1 ,支持兩位主要考慮到如果有三位版本號(hào),第三位一般是修復(fù)bug,很少會(huì)涉及配置的改動(dòng)。

      為了更好地講解代碼,下面會(huì)按照下圖的執(zhí)行邏輯逐段介紹:

      首先從第一個(gè)方法開(kāi)始 getStr(String key) 開(kāi)始:

      @Overridepublic String getStr(String key) { if (skipKey(key)) { return null; } if (this.properties == null) { synchronized (this) { if (this.properties == null) { this.init(); } } } return properties.getProperty(key);}

      這里是獲取配置值得入口,進(jìn)入方法后,首先經(jīng)過(guò) skipKey(key) 判斷:

      /** * 跳過(guò)讀取指定的 key * * @param key 屬性 */protected boolean skipKey(String key) { return getVersionKey().equals(key);}

      注意看圖中的 ① ,在后續(xù)初始化過(guò)程中,會(huì)通過(guò) ConfigHelper.getStr(getVersionKey()) 讀取用戶(hù)指定的版本,當(dāng)前配置文件也是 ConfigHelper.CONFIGS 中間的一環(huán),如果這里不做處理就會(huì)導(dǎo)致死循環(huán)的產(chǎn)生。

      接下來(lái)就是雙重加鎖方式的單例初始化, 為什么要在 getStr 中進(jìn)行初始化,為什么不放到構(gòu)造方法中?

      還是和后續(xù)的 ConfigHelper.getStr(getVersionKey()) 有關(guān),由于初始化會(huì)讀取配置,如果配置還沒(méi)有實(shí)例化,就會(huì)產(chǎn)生**“雞生蛋、蛋生雞”**的問(wèn)題,所以在構(gòu)造方法中沒(méi)有任何邏輯,單純創(chuàng)建了一個(gè)類(lèi),只有真正調(diào)用時(shí),才通過(guò)加鎖的方式初始化,此時(shí)所有實(shí)例都已經(jīng)存在,邏輯就能正常走下去。

      /** * 初始化 */protected void init() { Properties props = buildVersionProperties(); if (props != null) { this.properties = props; } else { this.properties = new Properties(); }}

      初始化中就是調(diào)用 buildVersionProperties() 方法進(jìn)行創(chuàng)建:

      protected Properties buildVersionProperties() { String version = ConfigHelper.getStr(getVersionKey()); // 讀取資源 URL resource = getClass().getResource(“”); if (resource == null) { return null; } if (resource.getProtocol().equals(“file”)) { if (resource.getPath().endsWith(“.jar”)) { try { JarFile jarFile = new JarFile(resource.getPath()); return chooseFromJarFile(jarFile, version); } catch (IOException e) { throw new RuntimeException(e); } } else { try { File file = new File(resource.toURI()); return chooseFromFile(file, version); } catch (Exception e) { throw new RuntimeException(e); } } } else if (resource.getProtocol().equals(“jar”)) { try { JarFile jarFile = ((JarURLConnection) resource.openConnection()).getJarFile(); return chooseFromJarFile(jarFile, version); } catch (IOException e) { throw new RuntimeException(e); } } return null;}

      方法的第一行代碼 ConfigHelper.getStr(getVersionKey()) 就是很關(guān)鍵的一個(gè)點(diǎn),在配置文件初始化過(guò)程中去獲取某個(gè)配置的值,此時(shí)方法會(huì)經(jīng)過(guò) JVM 實(shí)現(xiàn)、環(huán)境變量實(shí)現(xiàn)、用戶(hù)配置實(shí)現(xiàn),然后進(jìn)入到當(dāng)前的版本配置實(shí)現(xiàn)中,如果不加控制,就會(huì)在繼續(xù)進(jìn)入到 init 方法( synchronized 是可重入鎖,不會(huì)在此產(chǎn)生死鎖),然后再次調(diào)用 ConfigHelper.getStr(getVersionKey()) 形成一個(gè)死循環(huán)。

      破局的關(guān)鍵就是前面的 skipKey ,當(dāng)前類(lèi)發(fā)現(xiàn)找自己要 getVersionKey() 到時(shí),自己作為當(dāng)事人可以直接給出答案,也可以不給答案,然后在自己的后續(xù)邏輯中解決沒(méi)有配置的情況。這里直接不給答案,返回 null ,然后在 init 后續(xù)邏輯中處理,沒(méi)有指定版本號(hào)時(shí),默認(rèn)使用最新的配置文件。

      這個(gè)方法的下一行代碼是什么 URL resource = getClass().getResource(“”) ,讀取的就是當(dāng)前類(lèi)所在包的路徑,這種讀取方式就要求版本配置文件必須和配置類(lèi)在相同模塊的相同包下面,雖然這個(gè)限制了靈活性,但是可以避免在運(yùn)行時(shí)掃描所有類(lèi)路徑下的所有文件,可以有效的提升性能。

      這個(gè)方法在不同場(chǎng)景下運(yùn)行時(shí),獲取的數(shù)值不同,下面分幾種情況介紹。

      在源碼中(config項(xiàng)目)中執(zhí)行時(shí)

      此時(shí)讀取的 target 下面編譯的代碼,因此會(huì)是文件路徑,例如:

      file:/Users/xxx/mybatis-config/target/test-classes/io/mybatis/config/custom/

      這種情況下代碼就會(huì)執(zhí)行到 chooseFromFile(file, version); 代碼中:

      private Properties chooseFromFile(File file, String version) throws IOException { String configName = getConfigName(); File[] files = file.listFiles(); if (files == null || files.length == 0) { return null; } Map fileMap = new HashMap(); for (File f : files) { if (f.getName().startsWith(configName)) { fileMap.put(f.getName(), f); } } List versions = sortVersions(new ArrayList(fileMap.keySet())); ConfigVersion chooseVersion = chooseVersion(versions, version); return build(versions, chooseVersion, configVersion -> { try { return new FileInputStream(fileMap.get(configVersion.getFileName())); } catch (FileNotFoundException e) { return null; } });}

      這里就可以在當(dāng)前包路徑中直接 file.listFiles() 獲取目錄下面所有的文件,找到所有符合條件的文件后,根據(jù)文件創(chuàng)建排序后的 List versions ,后續(xù)邏輯在后面繼續(xù)說(shuō)。

      在作為依賴(lài) jar 包中執(zhí)行時(shí)

      當(dāng) mybatis-provider 作為依賴(lài)在 IDE 中(IDE不含mybatis-mapper源碼)運(yùn)行時(shí),看到的路徑如下:

      jar:file:/Users/xxx/.m2/repository/io/mybatis/mybatis-provider/2.0.0/mybatis-provider-2.0.0.jar!/io/mybatis/provider/config/

      后續(xù)處理和 Spring Boot 一樣,放在下面一起分析。

      沒(méi)有測(cè)試出 file:/…/xxx.jar 的情況

      在 Spring Boot 可執(zhí)行 Jar 包中執(zhí)行時(shí)

      打包后的 Spring Boot 通過(guò) java -jar 運(yùn)行時(shí),輸出的路徑如下:

      jar:file:/Users/xxx/mybatis-mapper-springboot/target/mybatis-mapper-example-springboot-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/mybatis-provider-2.0.0.jar!/io/mybatis/provider/config/

      和前一種情況相比,這里是在 fat jar 中的一個(gè) jar 包,多套了一層 jar。此時(shí)通過(guò)下面的方法獲取 Jar 文件:

      JarFile jarFile = ((JarURLConnection) resource.openConnection()).getJarFile()

      得到 Jar 文件后,就是調(diào)用 chooseFromJarFile(jarFile, version) 獲取配置文件:

      private Properties chooseFromJarFile(JarFile jarFile, String version) throws IOException { String configName = getConfigName(); String configPath = getConfigPath(); Enumeration entries = jarFile.entries(); Map entryMap = new HashMap(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String name = entry.getName(); if (name.startsWith(configPath)) { name = name.substring(configPath.length()); if (name.startsWith(configName)) { entryMap.put(name, entry); } } } List versions = sortVersions(new ArrayList(entryMap.keySet())); ConfigVersion chooseVersion = chooseVersion(versions, version); return build(versions, chooseVersion, configVersion -> { try { return jarFile.getInputStream(entryMap.get(chooseVersion.getFileName())); } catch (IOException e) { return null; } });}

      到了 sortVersions 方法后續(xù)就沒(méi)太大差別了:

      private List sortVersions(Collection fileNames) { if(fileNames == null || fileNames.isEmpty()) { return null; } Pattern pattern = Pattern.compile(getConfigName() + “-(vd+.d+)” + FILE_TYPE); return fileNames.stream().map(fileName -> { Matcher matcher = pattern.matcher(fileName); if (matcher.find()) { return new ConfigVersion(matcher.group(1), fileName); } return null; }).filter(Objects::nonNull).sorted().collect(Collectors.toList());}

      從文件名提取版本號(hào),轉(zhuǎn)換為有序的 ConfigVersion 集合。

      然后就是根據(jù)提供的版本選擇要使用的版本:

      private ConfigVersion chooseVersion(List versions, String version) { if (versions == null || versions.isEmpty()) { return null; } //沒(méi)有指定版本時(shí)使用最新版本 if (version == null || version.isEmpty()) { return versions.get(versions.size() – 1); } ConfigVersion configVersion = new ConfigVersion(version); //從最高版本進(jìn)行比較,選擇的版本高于或等于配置版本時(shí),就選擇該版本 for (int i = versions.size() – 1; i >= 0; i–) { if (configVersion.compareTo(versions.get(i)) >= 0) { return versions.get(i); } } //選擇的版本不高于所有版本時(shí),使用最小版本 return versions.get(0);}

      選擇出要優(yōu)先使用的版本后,后續(xù)就是根據(jù)下面規(guī)則創(chuàng)建配置:

      對(duì)應(yīng)的就是下面的方法:

      private Properties build(List versions, ConfigVersion chooseVersion, Function toInputStream) throws IOException { if (chooseVersion == null) { return null; } InputStream is; Properties prop = null; for (ConfigVersion configVersion : versions) { if(configVersion != chooseVersion) { prop = new Properties(prop); is = toInputStream.apply(configVersion); if(is != null) { prop.load(is); is.close(); } } } prop = new Properties(prop); is = toInputStream.apply(chooseVersion); if(is != null) { prop.load(is); is.close(); } return prop;}

      按照版本遞增的順序依次構(gòu)建 Properties ,小版本最為高版本的默認(rèn)值,在 Properties 中,當(dāng)前配置不存在時(shí),會(huì)從傳入的低版本查找:

      public String getProperty(String key) { Object oval = map.get(key); String sval = (oval instanceof String) ? (String)oval : null; Properties defaults; return ((sval == null) && ((defaults = this.defaults) != null)) ? defaults.getProperty(key) : sval;}

      最后再用選擇的版本作為優(yōu)先級(jí)最高的配置進(jìn)行初始化,到此就完成了整個(gè)配置初始化。

      到這里所有基本的功能都實(shí)現(xiàn)了,一個(gè)可擴(kuò)展的 Java 通用配置就成型了,其他組件可以簡(jiǎn)單的擴(kuò)展用戶(hù)配置和版本配置實(shí)現(xiàn)配置信息的管理。

      但是在目前 Spring Boot 流行的今天,我們大多數(shù)配置文件都在 application.properties 或 application.yaml 中,能否把自己的配置文件也放到 Spring 中一起管理呢?能否支持 Spring Boot 的命令行參數(shù)?支持 Spring Boot 的完整的外部化配置規(guī)則

      原文鏈接:https://blog.csdn.net/isea533/article/details/125657163

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶(hù)投稿
      上一篇 2022年7月11日 15:18
      下一篇 2022年7月11日 15:18

      相關(guān)推薦

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息