作者: "zyl910" 一、緣由 在項目開發時,因為運行環境的不同,導致有時得分別為不同的環境,切換配置參數打不同war包。但手工切換配置文件的話,不僅費時費力,而且容易出錯。 有些打包工具支持配置切換。這樣我們只要配好有那幾組參數,然後便可分別打war包了。但該辦法還是存在多個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點差異——
- 文件分隔符不同。Linux(等Unix類)系統用斜杠(
/
),而Windows系統用反斜杠(\
)。 - 根目錄寫法不同。Linux(等Unix類)系統是單根的
/
,而Windows系統有盤符的概念(如E:\
)。
2.2 辦法
對於操作系統的路徑格式區別,我們可以使用 System.getProperty("file.separator")
得到文件分隔符,使用 File.listRoots()
得到根目錄情況。根據這些信息,我們理論上能寫個函數,將約定好格式的路徑,給翻譯為當前操作系統的路徑格式。
但我後來測試File類時發現,其實有更簡單的辦法的。
File類的2點特性,對我們很有用——
- 在給File類的構造函數傳遞 Linux風格的路徑時,會自動轉為當前系統的文件分隔符。例如傳遞
/mysystem/app1/log
,隨後File構造好後實際為\mysystem\app1\log
。 - 在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點差異——
- 尾碼名不同。Linux系統用so,而Windows系統用dll。
- 文件基本名的命名習慣不同。Linux系統一般有個“lib”首碼。
4.2 辦法
假設動態庫的文件名都是符合命名規範的話,理論上是可以寫個函數將“lib.so”替換為“.dll”的。但是該辦法存在缺點——萬一遇到不符合規範的文件名就麻煩了。
所以建議採用這個辦法——在配置文件中分別給出不同操作系統的參數,然後java端判斷一下操作系統,選擇符合當前操作系統的參數。
不只是動態庫路徑,該辦法還能推廣任何的“Linux與Windows下的參數不同”問題,都可以按此辦法來處理。
隨後會遇到一個小問題——這種參數,因不同操作系統會有多個參數名(如 path.MyLib_windows
、path.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包的統一。
五、總結
回想一下本文的辦法,它其實是“約定大於配置”思想的一種實踐。
(完)
參考文獻
- System.getProperties() . http://tool.oschina.net/uploads/apidocs/jdk-zh/java/lang/System.html#getProperties()
- AbstractConfiguration . http://commons.apache.org/proper/commons-configuration/javadocs/v1.10/apidocs/org/apache/commons/configuration/AbstractConfiguration.html