PASX簡(jiǎn)化Java開發(fā)
發(fā)表時(shí)間:2024-02-14 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]你是否曾經(jīng)編寫了一個(gè)程序,卻在復(fù)檢的時(shí)候發(fā)現(xiàn)它的配置過程很不合理?你是否曾經(jīng)使用過配置文件,卻發(fā)現(xiàn)它們不能滿足描述應(yīng)用程序的需要?你是否為了解決幾個(gè)特殊問題而創(chuàng)建過臨時(shí)配置補(bǔ)丁,卻把花費(fèi)更多時(shí)間、開發(fā)普遍適用方案的希望寄托到了未來? 如果你的回答是肯定的,那么,你和大多數(shù)其他Java程序員...
你是否曾經(jīng)編寫了一個(gè)程序,卻在復(fù)檢的時(shí)候發(fā)現(xiàn)它的配置過程很不合理?你是否曾經(jīng)使用過配置文件,卻發(fā)現(xiàn)它們不能滿足描述應(yīng)用程序的需要?你是否為了解決幾個(gè)特殊問題而創(chuàng)建過臨時(shí)配置補(bǔ)丁,卻把花費(fèi)更多時(shí)間、開發(fā)普遍適用方案的希望寄托到了未來?
如果你的回答是肯定的,那么,你和大多數(shù)其他Java程序員一樣幸運(yùn),有一些工具能夠幫助你解決這些問題。如果你的回答是否定的,關(guān)于屬性文件局限的討論也許能夠讓你信服——還有更好的方法可供使用。
屬性文件是Java編程和運(yùn)行環(huán)境的一個(gè)重要組成部分。然而,當(dāng)一個(gè)程序員需要的功能遠(yuǎn)遠(yuǎn)超過Properties類提供的簡(jiǎn)單名字-值對(duì)時(shí),他需要有更豐富的表現(xiàn)手法。通常,Java程序員擴(kuò)展屬性文件的方法是為屬性本身的名字或值(或兩者同時(shí))增加額外的語(yǔ)義信息。很多時(shí)候,這種看來有效的方法會(huì)使問題越來越復(fù)雜。
為說明問題,請(qǐng)利用屬性把一系列的值賦給單個(gè)名字。讓我們假定你想要管理一組名稱服務(wù)器,可能采用的屬性文件內(nèi)容如下:
hosts_1=ns.foo.com
hosts_2=ns.bar.com
hosts_3=ns.acme.com
代碼很簡(jiǎn)單。改變名字-值對(duì)中名字的含義之后,你可以輕松地編寫出把“hosts_”開頭的名字當(dāng)成“hosts”列表中一個(gè)元素的程序。
下面,我們來看看一個(gè)更復(fù)雜的例子。假設(shè)你有同一Bean類InternetHost的兩個(gè)不同實(shí)例:實(shí)例A關(guān)聯(lián)到一個(gè)Web服務(wù)器的列表;實(shí)例B關(guān)聯(lián)到一個(gè)名稱服務(wù)器的列表。要從同一個(gè)文件配置這兩個(gè)實(shí)例,一種可能的方案如下:
name_hosts_1=ns.foo.com
name_hosts_2=ns.bar.com
name_hosts_3=ns.acme.com
web_hosts_1=www.foo.com
web_hosts_2=www.bar.com
web_hosts_3=www.acme.com
這種方法行得通,但總是給人以拼拼湊湊的感覺。如果你還不相信的話,稍微增加一點(diǎn)問題的復(fù)雜性:讓這些列表中的某個(gè)元素自己也成為一個(gè)列表;或者,使得下劃線字符(“_”)在名字-值對(duì)中合法。在這些情況下,簡(jiǎn)單的屬性文件變得非常復(fù)雜。
作為一個(gè)細(xì)心的讀者,你可能已經(jīng)發(fā)現(xiàn),InternetHost各個(gè)實(shí)例的命名方式逐漸模糊。為了把前三個(gè)屬性賦值給實(shí)例A,把后三個(gè)屬性賦值給實(shí)例B,你必須用某種與具體實(shí)例無關(guān)的方法告訴實(shí)例它們?cè)撚媚囊唤M屬性值。如果用直接編碼的方式,讓實(shí)例A尋找以“name_”開頭的屬性,讓實(shí)例B尋找以“Web_”開頭的屬性,那么,這兩個(gè)實(shí)例將不再屬于同一對(duì)象類。
最后的例子還顯示出另外一個(gè)問題。這就是,如何來調(diào)用實(shí)例A?簡(jiǎn)單地叫它“A”?到哪里去尋找它?它是本地實(shí)例還是遠(yuǎn)程接口?是否存在指向它的全局靜態(tài)引用?如是,如何訪問實(shí)例B(或者,那是否是“B”)?
解決這些問題的方案是使用一個(gè)組件配置和命名框架。有許多工具能夠幫助你完成這個(gè)任務(wù),其中之一就是PASX。PASX是一個(gè)源代碼開放的Java工具,它通過XML進(jìn)行配置,通過JNDI實(shí)現(xiàn)命名。PASX框架用XML配置用戶定義的服務(wù)、JNDI名稱空間、JDBC連接池、事件樹、工作隊(duì)列和系統(tǒng)日志。
PASX利用XML進(jìn)行配置,因?yàn)閄ML比簡(jiǎn)單的屬性列表具有更豐富的描述能力。為了理解為何XML更適合完成這類任務(wù),請(qǐng)?jiān)俅慰紤]第一個(gè)例子。如果用PASX定義的標(biāo)記重新描述,則結(jié)果應(yīng)該如下:
<List>
<String>ns.foo.com</String>
<String>ns.bar.com</String>
<String>ns.acme.com</String>
</List>
雖然代碼更加冗長(zhǎng),但它的含義比原來要清楚得多。由于一些列表可能被排序,元素在XML文檔中出現(xiàn)的次序決定了它們?cè)谧罱K數(shù)據(jù)結(jié)構(gòu)中的次序。屬性文件最終用來構(gòu)造Properties對(duì)象,它的名字必須指示出元素的索引,因?yàn)镻roperties對(duì)象直接從Hashtable派生得到。
用XML描述時(shí),第二個(gè)屬性示例如下所示:
<List name="name-servers">
<String>ns.foo.com</String>
<String>ns.bar.com</String>
<String>ns.acme.com</String>
</List>
<List name="web-servers">
<String>www.foo.com</String>
<String>www.bar.com</String>
<String>www.acme.com</String>
</List>
還記得示例二之后提出的難題嗎?讓列表中的某個(gè)元素成為一個(gè)子列表,讓其中一個(gè)列表成為元素名字可以包含下劃線的映射結(jié)構(gòu)。下面是它的答案:
<List name="name-servers">
<String>ns.foo.com<String>
<List>
<String>ns1.bar.com</String>
<String>ns2.bar.com</String>
</List>
<String>ns.acme.com</String>
</List>
<Map name="web-servers">
<String name="most_visited">www.foo.com<String>
<String name="most_bytes">www.bar.com</String>
</Map>
在PASX中,組件(一個(gè)類或者一組有著密切關(guān)系的類)是實(shí)現(xiàn)PASXService接口的Java Bean。它們由XML <Service>標(biāo)記定義,這個(gè)標(biāo)記用來命名組件的單個(gè)實(shí)例。賦予實(shí)例“A”名稱服務(wù)器列表以及賦予實(shí)例“B”Web服務(wù)器列表的XML代碼如下所示:
<Service name="A" class="my.InternetHost">
<List name="hosts">
<String>ns.foo.com</String>
<String<ns.bar.com</String>
<String<ns.acme.com</String>
</List>
</Service>
<Service name="B" class="my.InternetHost">
<List name="hosts">
<String>web.foo.com</String>
<String>web.bar.com</String>
<String>web.acme.com</String>
</List>
</Service>
PASX定義了一系列的標(biāo)準(zhǔn)XML標(biāo)記,用來聲明List、Map、Integer、String、Boolean等類型的屬性。然而,PASXService類還可以經(jīng)由名稱空間和XML模式使用它自己的XML標(biāo)記。XML模式允許組件開發(fā)者定義自己的標(biāo)記,允許XML解析器驗(yàn)證PASX所定義標(biāo)記和組件開發(fā)者所定義標(biāo)記的合法性。下面的示例模式定義了一個(gè)<Server>標(biāo)記,它必須有hostName和portNumber屬性。<Server>標(biāo)記必須作為<Cluster>標(biāo)記的子元素至少出現(xiàn)一次,但可以出現(xiàn)多次。<Cluster>標(biāo)記必須作為<ServerFarm>標(biāo)記的子元素出現(xiàn)至少一次,但可以出現(xiàn)多次。
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2000/10/XMLSchema"
xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
targetNamespace="http://pasx.org/PASX/CUSTOM-EXAMPLE" elementFormDefault="qualified" >
<annotation>
<documentation>
A custom schema example to be using with PASX (PCE)
</documentation>
</annotation>
<element name="Server">
<complexType content="empty">
<attribute
name="hostName"
use="required"
type="string"/>
<attribute
name="portNumber"
use="required"
type="positiveInteger"/>
</complexType>
</element>
<element name="Cluster">
<complexType>
<sequence>
<element
ref="pce:Server" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
<attribute
name="name"
use="required"
type="string"/>
</complexType>
</element>
<element name="ServerFarm">
<complexType>
<sequence>
<element
ref="pce:Cluster" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
<attribute
name="name"
use="required"
type="string"/>
</complexType>
</element>
<element name="PCE">
<complexType>
<sequence>
<element
ref="pce:ServerFarm" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
</schema>
詳細(xì)介紹XML模式文檔(XSD)的構(gòu)造方法已經(jīng)超出了本文的范圍。重要的是必須認(rèn)識(shí)到,PASXService組件的開發(fā)者可以使用一組定制標(biāo)記。更妙的是,開發(fā)者無需編寫任何驗(yàn)證代碼,就可以確保XML不僅格式良好而且合法(這一切由解析器完成)。下面聲明的<Service>標(biāo)記用到了前面的模式:
<Service
class="org.pasx.examples.CustomConfigExample"
name="examples.customConfigExample" >
<pce:PCE xmlns="http://pasx.org/PASX/CUSTOM-EXAMPLE"
xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
xsi:schemaLocation="http://pasx.org/PASX/CUSTOM-EXAMPLE /org/pasx/examples/custom-example.xsd" >
<ServerFarm name="farm0">
<Cluster name="cluster0">
<Server hostName="app0.foo.com" portNumber="8080" />
<Server hostName="app1.foo.com" portNumber="8080" />
</Cluster>
<Cluster name="cluster1">
<Server hostName="app2.foo.com" portNumber="8080" />
<Server hostName="app3.foo.com" portNumber="8080" />
</Cluster>
</ServerFarm>
<ServerFarm name="farm1">
<Cluster name="cluster0">
<Server hostName="app4.foo.com" portNumber="8080" />
<Server hostName="app5.foo.com" portNumber="8080" />
</Cluster>
<Cluster name="cluster1">
<Server hostName="app6.foo.com" portNumber="8080" />
<Server hostName="app7.foo.com" portNumber="8080" />
</Cluster>
</ServerFarm>
</pce:PCE>
</Service>
PASXService類如何使用XML配置信息實(shí)際上由類的開發(fā)者決定。在配置類的時(shí)候,它通過configure方法處理聲明它的XML元素(<Service>)。configure方法的特征如下:
public void configure(org.jdom.Element config,
Context context, ServiceManager caller)
注意Element參數(shù)是一個(gè)對(duì)JDOM Element的引用,而不是一個(gè)DOM Element的引用。JDOM比DOM更適合Java處理。但是,如果你需要DOM版本,JDOM包提供了轉(zhuǎn)換它們的方法。使用定制元素時(shí),類的開發(fā)者必須使用JDOM API訪問解析后的XML。然而,由于使用了XML模式,類開發(fā)者要關(guān)心的只是如何使用JDOM數(shù)據(jù)結(jié)構(gòu)中的信息,但無需編寫代碼去驗(yàn)證數(shù)據(jù)結(jié)構(gòu)的合法性(例如,<Cluster>元素只包含<Server>元素)。
如果PASXService的開發(fā)者決定只用PASX定義的XML標(biāo)記,且遵從Java Bean獲取和設(shè)置Bean屬性的模式,那么,他可以使用一個(gè)稱為XMLBeanUtil的工具類。這個(gè)類利用Bean的“內(nèi)省”機(jī)制,實(shí)現(xiàn)Bean屬性和PASX所定義XML標(biāo)記之間的匹配和賦值。它能夠讓代碼編寫變得非常輕松。例如:
public void configure( Element config,
Context context, ServiceManager caller )
{ xbu = new XMLBeanUtil( context ); xbu.populate( config, this );
}
如果某個(gè)PASXService組件由一組關(guān)系密切的小型Java類構(gòu)成,populate方法可能被多次調(diào)用。請(qǐng)考慮下面這個(gè)屬性:
<String name="lastName">Bushaw</String>
對(duì)于這個(gè)XML片斷,為了把lastName屬性賦給兩個(gè)不同的Bean,populate方法可以被多次調(diào)用:
Person father = new Person();
Person mother = new Person(); xbu.populate( config, father ); xbu.populate( config, mother );
使用XML模式和定制標(biāo)記的一個(gè)優(yōu)點(diǎn)是對(duì)預(yù)計(jì)配置的驗(yàn)證。換句話說,你可以編寫一個(gè)XML模式,使得對(duì)于給定的<Host>標(biāo)記,PortNumber和ipAddress屬性也必須同時(shí)指定。定義PASX標(biāo)記集的模式不能這么做,因?yàn)樗涣私飧鱾(gè)PASXService組件所需的語(yǔ)義信息。然而,XMLBeanUtil能夠跟蹤哪些Bean屬性已經(jīng)設(shè)置、哪些還沒有設(shè)置,而且允許在配置的時(shí)候指定一系列對(duì)屬性的約束。PASXService類的編寫者可以根據(jù)愛好、需要和方便程度,選用任意一種方法。
XMLBeanUtil把Bean屬性分成三類:Property(普通屬性),Dependent(依賴),Compliment(遵從)。如果這三類屬性的Java類型是List或Map,則它們都可以作為一個(gè)集合體設(shè)置。如果屬性已經(jīng)有值且被視為一個(gè)集合體,配置中的List或Map被加入到現(xiàn)有的屬性;如果它不被視為一個(gè)集合體,則配置中的List或Map將取代屬性值。下面的configure方法實(shí)例示范了它的用法。
public void configure( Element config,
Context context, ServiceManager caller )
{ xbu = new XMLBeanUtil( context ); xbu.addProperty( "hostName",
"Host Name Of Mythical TCP Service",
true, false ); xbu.addDependent( "portNumber",
"Port Number Of Mythical TCP Service",
"hostName", false ); xbu.addCompliment( "serverFarm",
"Back-end Server Farm",
"nameServers", true ); xbu.addCompliment( "nameServers",
"Name Servers To Resolve Against",
"serverFarm", true ); xbu.addProperty( "person",
"Person", true, false ); xbu.addProperty( "binaryThing",
"The binary input stream",
true, false ); xbu.addProperty( "props",
"Example Protomatter Properties",
true, false ); xbu.addProperty( "xml",
"An example JDOM XML Document",
true, false ); xbu.populate( config, this );
}
傳遞給addProperty、addDependent、addCompliment方法的屬性描述用于錯(cuò)誤信息。XMLBeanUtil類的checkService方法將檢查屬性是否已經(jīng)正確設(shè)置。如果還沒有,它將拋出一個(gè)異常,在錯(cuò)誤信息中利用屬性的描述。要查看完整的實(shí)例,請(qǐng)參考BeanServiceExample類。
作為一個(gè)細(xì)心的讀者,你可能已經(jīng)發(fā)現(xiàn),此前的所有例子都沒有提到JNDI。即使是<Service>標(biāo)記的name屬性也沒有提到任何有關(guān)JNDI的內(nèi)容。因此,你也許會(huì)疑惑PASX的命名部分如何使用JNDI。
默認(rèn)情況下,PASX使用內(nèi)存中的稱為PAS的扁平JNDI服務(wù)提供者(它來自底層的Protomatter包)。這個(gè)服務(wù)提供者僅僅是一個(gè)帶有JNDI接口的Hashtable。其他JNDI服務(wù)提供者可以通過URL命名指定甚至一起使用。因此,從一個(gè)RMI注冊(cè)器或者一個(gè)LDAP服務(wù)器(也就是rmi://localhost/creditCardAuthorizer或ldap://foo.com/uid=littlek,dc=foo,dc=com)指定一個(gè)URL是可能的。
在服務(wù)中使用JNDI的默認(rèn)JNDI動(dòng)作是bind。然而,通過可選的action屬性,動(dòng)作也可以指定為rebind或lookup。bind動(dòng)作嘗試把服務(wù)放入JNDI目錄,但如果目錄中已經(jīng)包含具有指定名字的服務(wù),則操作失敗。rebind動(dòng)作和bind動(dòng)作基本相同,例外之處在于,rebind動(dòng)作將覆蓋已經(jīng)存在的服務(wù)入口。無論是bind還是rebind動(dòng)作,PASX都會(huì)實(shí)例化PASXService對(duì)象。但對(duì)于lookup動(dòng)作,對(duì)象從JNDI目錄提取得到。
<Service>標(biāo)記中還有兩個(gè)屬性可能被用到:preRebind和postRebind。preRebind屬性指定一個(gè)名字或一個(gè)URL,它被用于在配置生效之前把對(duì)象重新綁定到JNDI目錄。類似地,postRebind屬性具有同樣的功能,但不同的是,它在配置之后出現(xiàn)。
結(jié)合運(yùn)用這些功能使得配置文件能夠輕松地完成一些有用的任務(wù)。它如,你可以從LDAP服務(wù)器“反串行化”一個(gè)對(duì)象,通過XML配置它,然后把它放入內(nèi)存,使得其他對(duì)象能夠訪問它?紤]這樣一個(gè)例子:某些業(yè)務(wù)過程的終止日期駐留在LDAP服務(wù)器上。下面的標(biāo)記代碼從LDAP服務(wù)器獲取終止日期,根據(jù)當(dāng)前的時(shí)區(qū)設(shè)置它,然后把它放入內(nèi)存:
<Service
name="ldap://foo.com/cn=expirationDate,o=foo.com"
class="my.UtilityDate"
action="lookup" postRebind="pas:expirationDate">
<Integer name="timeZone" value="-6"/>
</Service>
現(xiàn)在,其他服務(wù)可以通過<NamedService>標(biāo)記引用這個(gè)經(jīng)過配置的服務(wù)了。例如,考慮一個(gè)降價(jià)銷售廣告服務(wù),它必須知道降價(jià)銷售活動(dòng)何時(shí)終止:
<Service
name="pas:oriellySale"
class="my.SalesAdBanner">
<NamedService name="endDate" serviceName="pas:expirationDate"/>
</Service>
這并不是什么魔術(shù)。<NamedService>標(biāo)記只不過是根據(jù)serviceName屬性,讓PASX執(zhí)行一個(gè)JNDI查找,然后把結(jié)果放入名為endDate的Bean屬性。盡管如此,這種在配置時(shí)把一個(gè)對(duì)象連接到另一個(gè)對(duì)象的方法很有用。如果你開始使用這種方式,它可能改變你設(shè)計(jì)類的習(xí)慣。
PASX包含了各種各樣用于JNDI的工具。其中包括狀態(tài)和對(duì)象工廠,用于在目錄中串行化和保存對(duì)象狀態(tài);還有兩個(gè)服務(wù)提供者。請(qǐng)參考JavaDoc了解有關(guān)它們的更多信息。
另外,PASX還包含一個(gè)完整的實(shí)例包,包括源代碼、JavaDoc和伴隨PASX的配置文件。這些實(shí)例從簡(jiǎn)單的配置示例開始,涵蓋所有范圍,直至多個(gè)JNDI目錄的混合應(yīng)用。請(qǐng)參考JavaDoc org.pasx.examples。
我希望,本文所介紹概念的優(yōu)點(diǎn)已經(jīng)讓你確信:下一次坐下來編寫程序時(shí),不管其規(guī)模大小,你有必要考慮一下配置的需求和方法問題。本文所介紹的概念屬于中心主題,但PASX還包含本文沒有介紹的許多其他功能。PASX為完成許多任務(wù)提供了大量標(biāo)準(zhǔn)的服務(wù)和管理器,甚至還有一個(gè)Servlet框架,用戶可以通過這個(gè)Servlet框架管理服務(wù)和管理器。
最后,有一個(gè)稱為Potomac的工程。Potomac是一個(gè)基于PASX的源代碼開放Java軟件的集合。有了Potomac,所有你必須做的就是解開壓縮文件,這樣你就有了使用PASX時(shí)所有必須的Java組件,比如Protomatter和JDom,還有一些快速啟動(dòng)腳本和入門學(xué)習(xí)用的配置文件。