| 大家可能經(jīng)常會(huì)遇到在Java程序中存取程序配置文件的需求,比如,為了能夠和不同的數(shù)據(jù)庫(kù)連接,我們經(jīng)常把數(shù)據(jù)庫(kù)連接的信息存放到屬性文件中,這些信息一般包括數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序類名、數(shù)據(jù)庫(kù)連接的URL,數(shù)據(jù)庫(kù)的用戶名和口令等等。為了便于程序的安裝或部署,我們經(jīng)常會(huì)把這些的配置文件存放到程序安裝的根目錄中。由于Java程序用包來(lái)分組類,有時(shí)候?qū)⑦@些配置文件放入到讀取它們的類所在的包目錄中會(huì)更好一些。比如,在下面的圖示中,將數(shù)據(jù)庫(kù)配置文件 database.properties放到數(shù)據(jù)庫(kù)讀取類所在的包目錄就是一種比較清晰的存儲(chǔ)方案: /-----------其它包目錄 | | -------edu.ec.database | |---------------ConnectionPool(數(shù)據(jù)庫(kù)連接池類) | |---------------Dao(數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象類) | |---------------DaoFactory(Dao的工廠類) | |---------------database.properties(數(shù)據(jù)庫(kù)配置屬性文件) | |---------------RecordSet(記錄集類) 在這種存儲(chǔ)方案中,所有的與數(shù)據(jù)庫(kù)相關(guān)的類和配置文件都在同一個(gè)包目錄中;在開(kāi)發(fā)過(guò)程中,配置文件和源文件也按采用這種方式進(jìn)行組織,這樣會(huì)使得程序的數(shù)據(jù)庫(kù)訪問(wèn)維護(hù)變得相當(dāng)清晰明了。 大部分開(kāi)發(fā)工具在編譯打包這樣的源文件組織時(shí),會(huì)自動(dòng)將相關(guān)配置文件和類文件放到生成的目標(biāo)文件夾中或JAR文件中。一般情況下,我們?cè)诎l(fā)布自己的 Java程序時(shí),都是以JAR或WAR形式將程序打包發(fā)布,而對(duì)應(yīng)的配置文件也會(huì)按照上述的目錄格式被放到了JAR或WAR文件中,這樣,就實(shí)現(xiàn)了配置文件和程序文件打包在一起發(fā)布的目的。 現(xiàn)在的問(wèn)題是,我們?nèi)绾巫x取位于程序安裝文件中的配置文件的信息?比如,在上面的圖中, ConnectionPool是一個(gè)數(shù)據(jù)庫(kù)連接池類,它需要在系統(tǒng)啟動(dòng)時(shí)自動(dòng)讀取存儲(chǔ)在database.properties中的數(shù)據(jù)庫(kù)連接和配置信息,以便設(shè)置相關(guān)的數(shù)據(jù)庫(kù)連接。這樣,我們 就需要在程序中測(cè)定目前程序安裝或部署的位置,以便讀取對(duì)應(yīng)的屬性文件。 在很多其他語(yǔ)言編寫(xiě)的程序中,我們可以利用一些系統(tǒng)提供的API或一些全局對(duì)象獲取目前應(yīng)用程序運(yùn)行所在的目錄。比如VB,我們可以使用Application對(duì)象測(cè)定當(dāng)前程序的安裝位置,在Java程序中如何完成類似的任務(wù)呢? Java程序并沒(méi)有類似于VB那種全局對(duì)象,但如果我們觀察位于上述目錄結(jié)構(gòu)中的database.properties文件,應(yīng)該發(fā)現(xiàn)其處于應(yīng)用程序的CLASSPATH中,這樣,我們就可以使用Java中的類裝載器中的相關(guān)的方法,讀出這些配置文件的信息,該類名為ClassLoader。比如,在上例中,我們可以先得到ConnectinPool的類裝載器,然后測(cè)定ConnectionPool類所在的包路徑,然后利用 ConnectionPool所在的包目錄讀出database.properties文件的信息,對(duì)應(yīng)的偽代碼如下: ClassLoader loader=ConnectionPool.class.getClassLoader(); 得到ConnectionPool所在的包名; 將包名轉(zhuǎn)換為對(duì)應(yīng)的目錄名,存入一個(gè)名為path的字符串變量中; 調(diào)用loader的getResourceAsStream(path+"database.properties"),得到輸入流 下面是一個(gè)可實(shí)際運(yùn)行的樣例代碼片段,它可自動(dòng)測(cè)定傳入的類所在的包目錄,返回傳入的屬性文件所代表的輸入流。它還有一個(gè)附加的功能:如果屬性文件直接放到了當(dāng)前類所在的根目錄(比如位于JAR文件的根目錄或WAR文件的WEB-INF/classes目錄中)、系統(tǒng)的用戶目錄系統(tǒng)、系統(tǒng)其他的類路徑中時(shí),它也可以找到;當(dāng)然,如果還是找不到,它將返回null。具體的代碼如下: public class PropHelper{ /** *guessPropFile: *@param cls:和要尋找的屬性文件處于相同的包中的任意的類 *@param propFile:要尋找的屬性文件名 */ public static java.io.InputStream guessPropFile(Class cls,String propFile){ try{ //得到類的類裝載器 ClassLoader loader=cls.getClassLoader(); //先從當(dāng)前類所處路徑的根目錄中尋找屬性文件 java.io.InputStream in=loader.getResourceAsStream(propFile); if(in!=null) return in; //沒(méi)有找到,就從該類所處的包目錄中查找屬性文件 Package pack=cls.getPackage(); if(pack!=null){ String packName=pack.getName(); String path=""; if(packName.indexOf(".") < 0 ) path=packName+"/"; else{ int start=0,end=0; end=packName.indexOf("."); while(end!=-1){ path=path+packName.substring(start,end)+"/"; start=end+1; end=packName.indexOf(".",start); } path=path+packName.substring(start)+"/"; } in=loader.getResourceAsStream(path+propFile); if(in!=null) return in; } //如果沒(méi)有找到,再?gòu)漠?dāng)前系統(tǒng)的用戶目錄中進(jìn)行查找 java.io.File f=null; String curDir=System.getProperty("user.dir"); f=new java.io.File(curDir,propFile); if(f.exists()) return new java.io.FileInputStream(f); //如果還是沒(méi)有找到,則從系統(tǒng)所有的類路徑中查找 String classpath=System.getProperty("java.class.path"); String[] cps=classpath.split(System.getProperty("path.separator")); for(int i=0;i < cps.length; i++){ f=new java.io.File(cps[i],propFile); if(f.exists()) break; f=null; } if(f!=null) return new java.io.FileInputStream(f); return null; }catch(Exception e){throw new RuntimeException(e);} } } 使用舉例:利用上述的方法,可在ConnectionPool中自動(dòng)查找和ConnectionPool處于同一個(gè)包目錄中的database.properties的輸入流,并利用該輸入流讀入對(duì)應(yīng)的屬性值的代碼如下: public class ConnectionPool{ //靜態(tài)初始化器,將在ConnectionPools加載時(shí)自動(dòng)執(zhí)行 static{ java.util.Properties dbProp=new java.util.Properties(); java.io.InputStream in=PropHelper.guessPropFile(edu.ec.database.ConnectionPool.class,"database.properties"); if(in!=null) dbProp.load(in); //利用dbProp,為相應(yīng)的數(shù)據(jù)源對(duì)象設(shè)置相關(guān)的屬性,比如C3P0........ } } -------------------------------------------------------------------------------------------------------- 程序打包成Jar發(fā)布后,應(yīng)該將配置文件放在jar的當(dāng)前同級(jí)目錄,訪問(wèn)調(diào)用直接訪問(wèn),如  java.util.Properties pro = new java.util.Properties(); InputStream inStream = new java.io.FileInputStream("database.properties");pro.load(inStream); 如果是打包成WAR發(fā)布,則放在適合LZ的方法 ---------------------------------------------------------- 永久域名 http://hdwangyi. 2007-07-26 Java程序中的配置文件的存放和讀取關(guān)鍵字: 數(shù)據(jù)庫(kù) 屬性文件 類路徑大家可能經(jīng)常會(huì)遇到在Java程序中存取程序配置文件的需求,比如,為了能夠和不同的數(shù)據(jù)庫(kù)連接,我們經(jīng)常把數(shù)據(jù)庫(kù)連接的信息存放到屬性文件中,這些信息一般包括數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序類名、數(shù)據(jù)庫(kù)連接的URL,數(shù)據(jù)庫(kù)的用戶名和口令等等。為了便于程序的安裝或部署,我們經(jīng)常會(huì)把這些的配置文件存放到程序安裝的根目錄中。由于Java程序用包來(lái)分組類,有時(shí)候?qū)⑦@些配置文件放入到讀取它們的類所在的包目錄中會(huì)更好一些。比如,在下面的圖示中,將數(shù)據(jù)庫(kù)配置文件 database.properties放到數(shù)據(jù)庫(kù)讀取類所在的包目錄就是一種比較清晰的存儲(chǔ)方案: /-----------其它包目錄 | | -------edu.ec.database | |---------------ConnectionPool(數(shù)據(jù)庫(kù)連接池類) | |---------------Dao(數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象類) | |---------------DaoFactory(Dao的工廠類) | |---------------database.properties(數(shù)據(jù)庫(kù)配置屬性文件) | |---------------RecordSet(記錄集類) 在這種存儲(chǔ)方案中,所有的與數(shù)據(jù)庫(kù)相關(guān)的類和配置文件都在同一個(gè)包目錄中;在開(kāi)發(fā)過(guò)程中,配置文件和源文件也按采用這種方式進(jìn)行組織,這樣會(huì)使得程序的數(shù)據(jù)庫(kù)訪問(wèn)維護(hù)變得相當(dāng)清晰明了。 大部分開(kāi)發(fā)工具在編譯打包這樣的源文件組織時(shí),會(huì)自動(dòng)將相關(guān)配置文件和類文件放到生成的目標(biāo)文件夾中或JAR文件中。一般情況下,我們?cè)诎l(fā)布自己的 Java程序時(shí),都是以JAR或WAR形式將程序打包發(fā)布,而對(duì)應(yīng)的配置文件也會(huì)按照上述的目錄格式被放到了JAR或WAR文件中,這樣,就實(shí)現(xiàn)了配置文件和程序文件打包在一起發(fā)布的目的。 現(xiàn)在的問(wèn)題是,我們?nèi)绾巫x取位于程序安裝文件中的配置文件的信息?比如,在上面的圖中, ConnectionPool是一個(gè)數(shù)據(jù)庫(kù)連接池類,它需要在系統(tǒng)啟動(dòng)時(shí)自動(dòng)讀取存儲(chǔ)在database.properties中的數(shù)據(jù)庫(kù)連接和配置信息,以便設(shè)置相關(guān)的數(shù)據(jù)庫(kù)連接。這樣,我們 就需要在程序中測(cè)定目前程序安裝或部署的位置,以便讀取對(duì)應(yīng)的屬性文件。 在很多其他語(yǔ)言編寫(xiě)的程序中,我們可以利用一些系統(tǒng)提供的API或一些全局對(duì)象獲取目前應(yīng)用程序運(yùn)行所在的目錄。比如VB,我們可以使用Application對(duì)象測(cè)定當(dāng)前程序的安裝位置,在Java程序中如何完成類似的任務(wù)呢? Java程序并沒(méi)有類似于VB那種全局對(duì)象,但如果我們觀察位于上述目錄結(jié)構(gòu)中的database.properties文件,應(yīng)該發(fā)現(xiàn)其處于應(yīng)用程序的CLASSPATH中,這樣,我們就可以使用Java中的類裝載器中的相關(guān)的方法,讀出這些配置文件的信息,該類名為ClassLoader。比如,在上例中,我們可以先得到ConnectinPool的類裝載器,然后測(cè)定ConnectionPool類所在的包路徑,然后利用 ConnectionPool所在的包目錄讀出database.properties文件的信息,對(duì)應(yīng)的偽代碼如下: ClassLoader loader=ConnectionPool.class.getClassLoader(); 得到ConnectionPool所在的包名; 將包名轉(zhuǎn)換為對(duì)應(yīng)的目錄名,存入一個(gè)名為path的字符串變量中; 調(diào)用loader的getResourceAsStream(path+"database.properties"),得到輸入流 下面是一個(gè)可實(shí)際運(yùn)行的樣例代碼片段,它可自動(dòng)測(cè)定傳入的類所在的包目錄,返回傳入的屬性文件所代表的輸入流。它還有一個(gè)附加的功能:如果屬性文件直接放到了當(dāng)前類所在的根目錄(比如位于JAR文件的根目錄或WAR文件的WEB-INF/classes目錄中)、系統(tǒng)的用戶目錄系統(tǒng)、系統(tǒng)其他的類路徑中時(shí),它也可以找到;當(dāng)然,如果還是找不到,它將返回null。具體的代碼如下: public class PropHelper{ /** *guessPropFile: *@param cls:和要尋找的屬性文件處于相同的包中的任意的類 *@param propFile:要尋找的屬性文件名 */ public static java.io.InputStream guessPropFile(Class cls,String propFile){ try{ //得到類的類裝載器 ClassLoader loader=cls.getClassLoader(); //先從當(dāng)前類所處路徑的根目錄中尋找屬性文件 java.io.InputStream in=loader.getResourceAsStream(propFile); if(in!=null) return in; //沒(méi)有找到,就從該類所處的包目錄中查找屬性文件 Package pack=cls.getPackage(); if(pack!=null){ String packName=pack.getName(); String path=""; if(packName.indexOf(".") < 0 ) path=packName+"/"; else{ int start=0,end=0; end=packName.indexOf("."); while(end!=-1){ path=path+packName.substring(start,end)+"/"; start=end+1; end=packName.indexOf(".",start); } path=path+packName.substring(start)+"/"; } in=loader.getResourceAsStream(path+propFile); if(in!=null) return in; } //如果沒(méi)有找到,再?gòu)漠?dāng)前系統(tǒng)的用戶目錄中進(jìn)行查找 java.io.File f=null; String curDir=System.getProperty("user.dir"); f=new java.io.File(curDir,propFile); if(f.exists()) return new java.io.FileInputStream(f); //如果還是沒(méi)有找到,則從系統(tǒng)所有的類路徑中查找 String classpath=System.getProperty("java.class.path"); String[] cps=classpath.split(System.getProperty("path.separator")); for(int i=0;i < cps.length; i++){ f=new java.io.File(cps[i],propFile); if(f.exists()) break; f=null; } if(f!=null) return new java.io.FileInputStream(f); return null; }catch(Exception e){throw new RuntimeException(e);} } } 使用舉例:利用上述的方法,可在ConnectionPool中自動(dòng)查找和ConnectionPool處于同一個(gè)包目錄中的database.properties的輸入流,并利用該輸入流讀入對(duì)應(yīng)的屬性值的代碼如下: public class ConnectionPool{ //靜態(tài)初始化器,將在ConnectionPools加載時(shí)自動(dòng)執(zhí)行 static{ java.util.Properties dbProp=new java.util.Properties(); java.io.InputStream in=PropHelper.guessPropFile(edu.ec.database.ConnectionPool.class,"database.properties"); if(in!=null) dbProp.load(in); //利用dbProp,為相應(yīng)的數(shù)據(jù)源對(duì)象設(shè)置相關(guān)的屬性,比如C3P0........ } } 
 評(píng)論deadcode 寫(xiě)道 這個(gè)道路是不是有些曲折? 程序打包成Jar發(fā)布后,應(yīng)該將配置文件放在jar的當(dāng)前同級(jí)目錄,訪問(wèn)調(diào)用直接訪問(wèn),如 要是程序被打包了,要改個(gè)配置還要重新打包? 配置文件多的話還是集中起來(lái)管理比較直觀,而且很多可以在系統(tǒng)啟動(dòng)時(shí)先加載,一般也就一次. java.util.Properties pro = new java.util.Properties(); InputStream inStream = new java.io.FileInputStream("database.properties");pro.load(inStream); 如果是打包成WAR發(fā)布,則放在適合LZ的方法 這個(gè)道路是不是有些曲折?  要是程序被打包了,要改個(gè)配置還要重新打包? 配置文件多的話還是集中起來(lái)管理比較直觀,而且很多可以在系統(tǒng)啟動(dòng)時(shí)先加載,一般也就一次.     現(xiàn)在,guessPropFile可以自動(dòng)在如下位置中尋找屬性文件:  
 其實(shí),guessPropFile方法還可做的更加智能一些,比如在WAR文件的WEB-INF文件中尋找屬性文件。如果要完成這個(gè)任務(wù),ClassLoader是關(guān)鍵類,利用它的getResource方法,就可以獲得相應(yīng)程序中的資源的位置,我們可以在它的基礎(chǔ)上稍加修改就可獲得當(dāng)前Java程序的運(yùn)行位置信息,其偽代碼如下: 獲得Class參數(shù)的所在的類名 取得該類所在的包名 將包名轉(zhuǎn)換為路徑 利用getResource得到當(dāng)前的類文件所在URL 利用URL解析出當(dāng)前Java程序所在的路徑 具體代碼如下: 
 在getAppPath方法的幫助下,我們可以測(cè)定當(dāng)前程序運(yùn)行的路徑是不是包含WEB-INF/classes路徑信息,如果是,則當(dāng)前程序就可能是運(yùn)行在Web環(huán)境中,這樣,就可以對(duì)guessPropFile中的代碼進(jìn)行增加,加入在WEB-INF文件夾尋找屬性文件的方法。 | 
|  | 
來(lái)自: WindySky > 《制作JAVA安裝文件》