[Java] Windows/Linux路徑不同時,統一war的最簡辦法


作者: zyl910

一、緣由

在項目開發時,因為運行環境的不同,導致有時得分別為不同的環境,切換配置參數打不同war包。但手工切換配置文件的話,不僅費時費力,而且容易出錯。
有些打包工具支持配置切換。這樣我們只要配好有那幾組參數,然后便可分別打war包了。但該辦法還是存在多個war文件易搞錯的問題。而且因為生產環境一般有Windows、Linux 2類操作系統,導致生產環境的war也得分別搞2套,這是否真的有必要。

所以我們希望能統一war。即僅打包一個war,而該war能在各種環境下運行。
雖然可以使用“war 配置文件分離”、“將配置參數放到數據庫”等辦法來實現統一war。但那些辦法比較復雜,而且仍會引起配置文件種類過多問題。

分析了一下 Windows、Linux 不同的配置參數,發現它們一般都是因為路徑不同而被迫做成參數配置的。
於是我針對這種情況,找到了一些簡單、有效的處理辦法。使它們在Windows、Linux切換時不用更改參數,有利於統一war。

二、統一路徑寫法

2.1 問題

對於Windows、Linux 不同的配置參數,最常見的是日志文件的路徑。例如以下分別是 Linux、Windows 下的日志目錄——

/mysystem/app1/log
E:\mysystem\app1\log

可見,這2種目錄的目錄結構是類似,僅是因為 Linux、Windows 的路徑格式不同,而有了2點差異——

  1. 文件分隔符不同。Linux(等Unix類)系統用斜杠(/),而Windows系統用反斜杠(\)。
  2. 根目錄寫法不同。Linux(等Unix類)系統是單根的 /,而Windows系統有盤符的概念(如 E:\)。

2.2 辦法

對於操作系統的路徑格式區別,我們可以使用 System.getProperty("file.separator") 得到文件分隔符,使用 File.listRoots() 得到根目錄情況。根據這些信息,我們理論上能寫個函數,將約定好格式的路徑,給翻譯為當前操作系統的路徑格式。

但我后來測試File類時發現,其實有更簡單的辦法的。
File類的2點特性,對我們很有用——

  1. 在給File類的構造函數傳遞 Linux風格的路徑時,會自動轉為當前系統的文件分隔符。例如傳遞 /mysystem/app1/log,隨后File構造好后實際為 \mysystem\app1\log
  2. 在Windows下通過File類打開文件流時,若路徑中沒有盤符,則會自動選擇當前工作目錄(user.dir)的盤符。例如對於 \mysystem\app1\log,假設當前工作目錄是E盤,那么實際的路徑是 E:\mysystem\app1\log

即File類會自動將 Linux(等Unix類)系統風格的路徑,轉為Windows風格的路徑。只要我們能保證工作目錄的所在盤符,就是所需的盤符。

2.3 應用:logback的日志路徑

2.3.1 之前

之前在 logback.xml 文件中是這樣指定路徑的。

<property name="LOG_HOME" value="/mysystem/app1/log" />
<!-- <property name="LOG_HOME" value="E:\mysystem\app1\log" /> -->

它默認用Linux的路徑參數,而Windows的路徑參數是處於被注釋的狀態。
然后在需要部署到Windows系統時,調整一下注釋使第2行生效,並根據實際情況調整一下盤符。

2.3.2 之后

現在 logback.xml 文件中只需寫Linux目錄就行。

<property name="LOG_HOME" value="/mysystem/app1/log" />

war包一般是在tomcat等web容器中運行的。對於Windows下,工作目錄的盤符就是web容器所在盤符。

  • 假設該war部署在E盤的tomcat上的,那么配置文件中的 /mysystem/app1/log,實際上是 E:\mysystem\app1\log
  • 假設該war部署在F盤的weblogic上的,那么配置文件中的 /mysystem/app1/log,實際上是 F:\mysystem\app1\log

……

2.4 小結

統一路徑寫法是非常簡單的,即只保留Linux(Unix類)路徑寫法就行。這樣大多數程序都能正常工作的。

三、不支持統一路徑寫法時

3.1 問題與思路

有少量程序是不支持統一路徑寫法的寫法(可能是因為它們沒有使用File類來處理文件路徑,而是手工拼接)。這時該怎么辦呢?

退回之前的“配置切換”法是不行的,容易造成參數復雜等問題。
因Java中能判斷操作系統版本,故可以考慮寫個函數,將統一路徑寫法轉為當前操作系統的格式。這樣便解決問題了。

System.getProperty可獲取系統屬性——

  • os.name: 操作系統的名稱。例如Windows系統都是以 windows 開頭。
  • user.dir: 用戶的當前工作目錄。

3.2 代碼

    /** 判斷是不是Windows系統.
    *
    * @return    返回是不是Windows系統.
    */
    private static boolean isOsWindows() {
        String osname = System.getProperty("os.name").toLowerCase();
        boolean rt = osname.startsWith("windows");
        return rt;
    }

    /** 將路徑修正為當前操作系統所支持的形式.
    *
    * @param path    源路徑.
    * @return    返回修正后的路徑.
    */
    public static String fixPath(String path) {
        if (null==path) return path;
        if (path.length()>=1 && ('/'==path.charAt(0) || '\\'==path.charAt(0))) {
            // 根目錄, Windows下需補上盤符.
            if (isOsWindows()) {
                String userdir = System.getProperty("user.dir");
                if (null!=userdir && userdir.length()>=2) {
                    return userdir.substring(0, 2) + path;
                }
            }
        }
        return  path;
    }

於是可利用fixPath函數,將配置中讀到的路徑,轉為當前操作系統的格式。

四、Linux與Windows下的參數不同時

4.1 問題與思路

上面的辦法主要是適合於路徑結構相同時。可是有些時候,Linux與Windows下的參數不同,例如動態庫的路徑——

/mysystem/app1/libMyLib.so
E:\mysystem\app1\MyLib.dll

它們主要是有這2點差異——

  1. 后綴名不同。Linux系統用so,而Windows系統用dll。
  2. 文件基本名的命名習慣不同。Linux系統一般有個“lib”前綴。

4.2 辦法

假設動態庫的文件名都是符合命名規范的話,理論上是可以寫個函數將“lib.so”替換為“.dll”的。但是該辦法存在缺點——萬一遇到不符合規范的文件名就麻煩了。

所以建議采用這個辦法——在配置文件中分別給出不同操作系統的參數,然后java端判斷一下操作系統,選擇符合當前操作系統的參數。
不只是動態庫路徑,該辦法還能推廣任何的“Linux與Windows下的參數不同”問題,都可以按此辦法來處理。

隨后會遇到一個小問題——這種參數,因不同操作系統會有多個參數名(如 path.MyLib_windowspath.MyLib_linux)。當取參數時,若都將這些參數名傳過去,代碼會變得很臃腫。而且不易擴充操作系統類型。
所以建議制定一下規范——帶“._windows”后綴的是Windows特有參數,否則則是默認參數(Linux的)。這樣便簡化了參數名傳遞,且有利於未來增加操作系統的支持。

代碼如下——

    /** 取得字符串_自動選擇操作系統的專用參數, 具有 defaultValue 參數.
    *
    * <ul>
    *    <li>當為Windows時, 優先讀取 {@code key + "._windows"} 參數, 找不到時才用 {@code key} .</li>
    * </ul>
    *
    * @param config    配置對象.
    * @param key    的鍵名.
    * @param defaultValue    默認值.
    * @return    返回所找到的參數值, 找不到時返回 defaultValue .
    */
    public static String getString(AbstractConfiguration config, String key, String defaultValue) {
        String rt = null;
        if (isOsWindows()) {
            String key2 = key+"._windows";
            rt = config.getString(key2, null);
        }
        if (null==rt) {
            rt = config.getString(key, defaultValue);
        }
        return rt;
    }

    /** 取得字符串_自動選擇操作系統的專用參數, 無 defaultValue 參數.
    *
    * <ul>
    *    <li>當為Windows時, 優先讀取 {@code key + "._windows"} 參數, 找不到時才用 {@code key} .</li>
    * </ul>
    *
    * @param config    配置對象.
    * @param key    的鍵名.
    * @return    返回所找到的參數值, 找不到時返回空串.
    */
    public static String getString(AbstractConfiguration config, String key) {
        return  getString(config, key, null);
    }

    /** 取得路徑字符串_自動選擇操作系統的專用參數, 具有 defaultValue 參數. Windows下會自動將根目錄(/)轉為當前盤符的根路徑(E:/) .
    *
    * <ul>
    *    <li>當為Windows時, 優先讀取 {@code key + "._windows"} 參數, 找不到時才用 {@code key} .</li>
    * </ul>
    *
    * @param config    配置對象.
    * @param key    的鍵名.
    * @param defaultValue    默認值.
    * @return    返回所找到的參數值, 找不到時返回 defaultValue .
    */
    public static String getStringPath(AbstractConfiguration config, String key, String defaultValue) {
        String rt = getString(config, key, defaultValue);
        rt = fixPath(rt);
        return rt;
    }

    /** 取得路徑字符串_自動選擇操作系統的專用參數, 無 defaultValue 參數. Windows下會自動將根目錄(/)轉為當前盤符的根路徑(E:/) .
    *
    * <ul>
    *    <li>當為Windows時, 優先讀取 {@code key + "._windows"} 參數, 找不到時才用 {@code key} .</li>
    * </ul>
    *
    * @param config    配置對象.
    * @param key    的鍵名.
    * @return    返回所找到的參數值, 找不到時返回空串.
    */
    public static String getStringPath(AbstractConfiguration config, String key) {
        return  getStringPath(config, key, null);
    }

4.3 用法

4.3.1 之前

之前靠注釋來切換所需配置的。

path.MyLib=/mysystem/app1/libMyLib.so
#path.MyLib=E:\mysystem\app1\MyLib.dll

它默認用Linux的參數,而Windows的參數是處於被注釋的狀態。
然后在需要部署到Windows系統時,調整一下注釋使第2行生效。(已經利用之前的內容,使用統一路徑寫法)

4.3.2 之后

現在可直接在配置文件中寫上這2個參數。注意給Windows版參數加上“._windows”后綴。

path.MyLib=/mysystem/app1/libMyLib.so
path.MyLib._windows=/mysystem/app1/MyLib.dll

然后在程序中這樣獲取配置了。

String pathMyLib = getStringPath(config, "path.MyLib");

隨后war運行時,會自動根據當前操作系統,選取自己的參數。 保證了war包的統一。

五、總結

回想一下本文的辦法,它其實是“約定大於配置”思想的一種實踐。

(完)

參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM