還在使用XML僅作為簡單讀寫的配置文件嗎?其實XML還有更好更實用的操作…
概述 ????在Java中使用XML可謂是極為簡單,JDOM問世把Java與XML的關系更近了。但是JDOM提供對于XML簡單的靜態操作。如果把XML作為動態配置文件或者模塊安裝配置文件(至少在我開發的系統中作為),僅僅使用JDOM的方法就顯得有點兒力不從心了,因此,我們要充分利用JDOM的優點開發出對XML文件的高級操作庫。
????我們要開發什么樣的操作庫呢?首先,看下面的XML信息:
<?xml?version="1.0"?encoding="UTF-8"?> <config> ??<dirs> ????<home>/home/myuser</home> ????<tmp>/home/myuser/tmp</tmp> ????<var>/home/myuser/var</var> ??</dirs> </config>
????這是一個簡單的配置文件,其中重復使用主目錄路徑信息,很麻煩不是嗎?不過這里有很好的解決方案就是使用XML內部實體,如下:
<?xml?version="1.0"?encoding="UTF-8"?> <!DOCTYPE?config?[ ?<!ENTITY?home?"/home/myuser?"> ]> <config> ??<dirs> ????<home>&home;?</home> ????<tmp>&home;?/tmp</tmp> ????<var>&home;?/var</var> ??</dirs> </config>
????這是個不錯的辦法,通過定義實體可是替換那些重復使用的信息。不過我還是要實現自己的方法,至于它們的區別,我會在下面講述。 變量 ????因為我是程序員,看到這樣的問題首先會想到"變量"這個詞。為什么不使用一個特殊的元素作為變量的定義呢?OK,開始讓我們實現吧。請看下面的示例:
<?xml?version="1.0"?encoding="UTF-8"?> <config> ??<property?name="home"?value="/home/myuser"/> ??<dirs> ????<!-這段可以省略了' ????<home>${home}?</home> ????<property?name="home"?value="${home}/tmp"/> ????<tmp?path="${dirs.home}"> ??????<tmp1>${dirs.home}/tmp1</tmp1> ????</tmp> ????<var>${home}/var</var> ??</dirs> </config>
????在這里我使用了property元素作為變量的定義元素。咦?看懂了沒有?有兩個home變量啊,那該怎么解決沖突呢?正如上面顯示的,使用?元素名稱+"."+變量名稱?作為該變量的定義以解決沖突如何?那就這么做吧。
????OK,配置變量規則都設計出來了該寫解釋器了。
因為可能以后還會增加額外的解釋器,我們先定義一個解釋器接口DocumentParser:
import?org.jdom.Document; public?interface?DocumentParser{ ??public?void?parse(Document?doc); }
????既然接口已定義好,就該寫變量解釋器了。
/?**? *?property元素解釋器。 */ public?class?DocumentPropertyParser?implements?DocumentParser{ ??private?Properties?variables;
??public?DocumentPropertyParser(){ ????variables?=?new?Properties(); ??}
??public?void?parse(Document?doc){ ????Element?element?=?doc.getRootElement(); ????String?path?=?element.getName(); ????//解析變量并添加到variables屬性對象中 ????parseVariables(path,?element); ????//分析元素屬性和值,?替換已解析的變量. ????parseContent(element); ??}
??/?** ???*?解析變量方法 ???*/ ??private?void?parseVariables(String?path,?Element?element){ ????List?subList?=?element.getChildren(); ????Iterator?elementI?=?subList.iterator(); ????while(elementI.hasNext()){ ??????Element?elementS?=?(Element)elementI.next(); ??????String?name?=?elementS.getName();
??????//處理變量,?這里忽略了property元素名稱的大小寫. ??????//否則,?遞歸處理里面的元素. ??????if("property".equalsIgnoreCase(name)){
????????//獲取變量名稱. ????????String?propName?=?elementS.getAttributeValue("name");
????????//如果變量名稱不為空,?則先獲取名稱為value的屬性. ????????if(propName?!=?null){ ??????????String?propValue?=?elementS.getAttributeValue("value");
??????????//如果名稱為value的屬性不存在,?則獲取該元素的值. ??????????if(propValue?==?null)?propValue?=?elementS.getText();
??????????//如果其值也不存在,?則該變量的值取空. ??????????if(propValue?==?null)?propValue?=?"";
??????????//向變量列表中加入該變量信息. ??????????variables.setProperty(path?+?"."?+?propName,?propValue); ????????} ????????//刪除屬性字段,?避免沖突. ????????elementI.remove(); ??????}?else{ ????????parseVariables(path?+?"."?+?name,?elementS); ??????} ????} ??}
??/?** ???*?處理元素中的變量. ???*/ ??private?void?parseContent(Element?element){ ????//首先,?檢查該元素屬性值是否存在變量. ????List?attributes?=?element.getAttributes(); ????Iterator?attributeI?=?attributes.iterator(); ????while(attributeI.hasNext()){ ??????Attribute?attribute?=?(Attribute)attributeI.next(); ??????String?attributeValue?=?attribute.getValue();
??????//使用字符串替換方法替換變量. ??????Enumeration?propNames?=?variables.propertyNames(); ??????while(propNames.hasMoreElements()){ ????????String?propName?=?(String)propNames.nextElement(); ????????String?propValue?=?variables.getProperty(propName); ????????attributeValue?=?StrUtil.replace("${"?+?propName?+?"}", ?????????????????????????????????????????propValue, ?????????????????????????????????????????attributeValue); ??????} ?????? ??????//重新設置替換過的變量值. ??????attribute.setValue(attributeValue); ????} ????//如果有子信息,?遞歸檢索子信息,?否則檢索其值. ????List?subElements?=?element.getChildren(); ????if(subElements.size()?!=?0){ ??????//遞歸檢索Element. ??????Iterator?subElementI?=?subElements.iterator(); ??????while(subElementI.hasNext()){ ????????Element?subElement?=?(Element)subElementI.next(); ????????parseContent(subElement); ??????} ????}?else{ ??????String?value?=?element.getText(); ??????if(value?!=?null){ ????????//覆蓋變量. ????????Enumeration?propNames?=?variables.propertyNames(); ????????while(propNames.hasMoreElements()){ ??????????String?propName?=?(String)propNames.nextElement(); ??????????String?propValue?=?variables.getProperty(propName); ??????????value?=?StrUtil.replace("${"?+?propName?+?"}",?propValue,?value); ????????} ????????element.setText(value); ??????} ????} ??} }
注:StrUtil.replace方法是一般字符串替換方法,自行實現。
????OK,簡單的變量解釋器完工了。 ???? ????再想想還需要什么…
系統變量 ????有時候,配置文件需要一些系統的信息作為參考(我稱為系統變量),而系統信息是在運行期才可以得到的,不過我們可以通過預定義一些變量名稱使在配置文件中可以提前使用,通過解釋系統變量來動態為該元素賦值。例如這個配置文件:
<?xml?version="1.0"?encoding="UTF-8"?> <config> ??<property?name="version"?value="1705"/> ??<module?version="%{system.version}.${version}"?home="%{system.home}/helloword"> ????…?… ??</module> </config>
????解析后變成了:
<?xml?version="1.0"?encoding="UTF-8"?> <config> ??<module?version="1.0.1705"?home="/home/myuser/helloworld"> ????…?… ??</module> </config>
????這里使用了我們預定義的系統變量"system.version"和"system.home"分別表示系統版本和系統的主目錄。這使得配置文件具備獲取系統動態信息的能力。下面我們用Java實現系統變量解釋器DocumentConstantParser:
public?final?class?DocumentConstantParser?implements?DocumentParser{ ??private?Properties?constants;
??public?DocumentConstantParser(){ ????constants?=?new?Properties(); ??}
??public?DocumentConstantParser(Properties?original){ ????constants?=?new?Properties(original); ??}
??//通過運行期設置預定義的變量,?配置文件可以動態讀取這些信息. ??public?void?addConstant(String?name,?String?value){ ????constants.setProperty(name,?value); ??}
??public?void?removeConstant(String?name){ ????constants.remove(name); ??}
??public?void?parse(Document?doc){ ????parseContent(doc.getRootElement()); ??} ??… }
因為parseContent方法跟變量解釋器的幾乎相同,所以節省點兒空間就不寫出了。系統可以通過使用addConstant和removeConstant方法操作對XML文檔預定義的變量,這使得XML的配置功能更強大了。
比較前述的使用實體,此方法易于跟系統交互,且方法簡便易懂。
命令 ????曾經研究過Ant,其中的<javac>等元素十分吸引我。能不能再添加一個具有命令解釋能力的元素呢?答案是肯定的。 ???? ????我定義XML文檔中的command元素作為命令的入口元素。通過實現具體命令實現執行該命令。首先,因為可能有很多命令,我們定義一個命令接口DocumentCommand:
public?interface?DocumentCommand{
??//檢查是否接受指定名稱的命令。 ??public?boolean?accept(String?name);
??//處理命令 ??public?void?invoke(Document?doc,?Element?current,?DocumentCommandParser?parser)? throws?JDOMException; }
定義好了命令解釋接口,就可以設計解釋器了。Java代碼如下:
public?final?class?DocumentCommandParser?implements?DocumentParser{ ??private?HashSet?commands; ??private?boolean?parsing; ??public?DocumentCommandParser(){ ????commands?=?new?HashSet(10); ????parsing?=?false; ??}
??//添加命令解釋單元 ??public?synchronized?void?addCommand(DocumentCommand?dc){ ????if(parsing) ??????try{ ????????wait(); ??????}catch(InterruptedException?ie){}
????if(commands.contains(dc)) ??????commands.remove(dc); ????commands.add(dc); ??}
??//刪除命令解釋單元 ??public?synchronized?void?removeCommand(DocumentCommand?dc){ ????if(parsing) ??????try{ ????????wait(); ??????}catch(InterruptedException?ie){} ????commands.remove(dc); ??}
??public?synchronized?void?parse(Document?doc)?throws?JDOMException{ ????parsing?=?true; ????parseCommand(doc,?doc.getRootElement()); ????parsing?=?false; ????notifyAll(); ??}
??private?void?parseCommand(Document?doc,?Element?element)?throws?JDOMException{ ????List?sub?=?element.getChildren(); ????for(int?i?=?0;i?<?sub.size();i++){ ??????Element?subElement?=?(Element)sub.get(i);
??????//檢查該元素是否是命令元素,?如果不是遞歸解釋該元素的子元素. ??????if(!"command".equalsIgnoreCase(subElement.getName())){ ????????parseCommand(doc,?subElement); ??????}?else{
????????//刪除命令元素,?避免沖突. ????????sub.remove(i--);
????????//獲取命令名稱,如果為空,?則繼續. ????????String?commandName?=?subElement.getAttributeValue("name"); ????????if(commandName?!=?null){
??????????//開始檢索適合的命令處理器. ??????????Iterator?cmdI?=?commands.iterator(); ??????????processing: ??????????while(cmdI.hasNext()){ ????????????DocumentCommand?dc?=?(DocumentCommand)cmdI.next();
????????????//找到對應命令處理器后,?進行命令解釋. ????????????if(dc.accept(commandName)){
??????????????//調用命令處理方法,?如果執行成功,?刪除該變量. ??????????????dc.invoke(doc,?subElement,?this);
??????????????//中斷命令檢索過程. ??????????????break?processing; ????????????} ??????????} ????????} ??????} ????} ??} }
????終于實現了命令解釋功能,下面來研究一下它的用途。 ????例如,一個文件拷貝命令filecopy有兩個屬性,source為源文件路徑,target為拷貝目的路徑,通過這一命令可以實現眾多功能,如組件安裝時從其他空間拷貝數據到其主目錄。代碼如下:
…?… ??<command?name="filecopy"?source="${home}?/lib"?target="%{system}/lib/module"/>? …?…
????這樣,組件所使用到的庫文件就拷貝到了系統的庫中。下面是filecopy的代碼:
public?class?FileCopyCommand?implements?com.yipsilon.util.jdom.DocumentCommand{ ????public?boolean?accept(String?s){ ?????? ??????//只有filecopy命令可以接受. ??????return?"filecopy".equals(s); ????}
????public?void?invoke(Document?document,?Element?element,?DocumentCommandParser?parser){ ??????String?name?=?element.getAttributeValue("name");?????//值為:?filecopy ??????String?source?=?element.getAttributeValue?("source");?//值為原路徑 ??????String?target?=?element.getAttributeValue?("target");?//值為目的路徑 ??????FileUtil.copy(source,?target); ??????//因內容有限,?這里沒有增加額外的錯誤檢查. ????} ??}
注:FileUtil.copy是簡單的文件拷貝方法,?請自行實現。
結論 ????通過在XML文檔中使用變量、系統變量、命令大大增強了XML作為配置信息集合的功能。通過實現不同的解釋器和命令單元,可以使其功能不斷增多從而實現一些以前無法實現的目的,比如作為安裝文件進行環境檢測、I/O操作等,既能提高開發速度,內容也好管理。
????Java與XML與生俱來,在它們的身上可以開發出很多簡化開發的產品,如Ant。有時候只需要動動腦筋就可以使開發變得簡單。
|
|