XDoclet起步
XDoclet是一個代碼生成工具,它可以把你從Java開發(fā)過程中繁重的重復勞動中解脫出來。XDoclet可以讓你的應用系統(tǒng)開發(fā)的更加快速,而你只要付比原先更少的努力。你可以把你手頭上的冗長而又必需的代碼交給它幫你完成,你可以逃脫“deployment?descriptor地獄”,你還可以使你的應用系統(tǒng)更加易于管理。而你所要做的,只不過是在你的注釋里,多加一些類javadoc屬性。然后,你會驚訝于XDoclet為了做到的一切。
討論XDoclet,有一點比較容易產(chǎn)生混淆,那就是XDoclet不但是一系統(tǒng)的代碼生成應用程序,而且它本身還是一個代碼生成框架。雖然每個應用系統(tǒng)的細節(jié)千變?nèi)f化(比如EJB代碼生成和Struts代碼生成是不一樣的,而JMX代碼生成又是另一番景象),但這些代碼生成的核心概念和用法卻是類似的。
在這一章里,我們將會看到滲透到所有XDoclet代碼生成程序當中的XDoclet框架基礎(chǔ)概念。但在之前,我們先從一個例子入手。
2.1?XDoclet?in?action
每一個程序員都會認識到,他們的程序永遠也不會完成。總會有另一些的功能需要添加,另一些的BUG需要修正,或者需要不斷的進行重構(gòu)。所以,在代碼里添加注釋,提醒自己(或者其他的程序員)有哪些任務需要完成已成為一個共識。
如何來跟蹤這些任務是否完成了呢?理想情況下,你會收集整理出來一個TODO任務列表。在這方面,XDoclet提供了一個強大的TODO生成器,來幫助你完成這個任務。這是一個把XDoclet引入項目的好機會。
2.1.1?一個公共的任務
假設你正在開發(fā)一個使用了勺子的類。
public?class?Matrix?{
//?TODO???需要處理當沒有勺子的情況
public?void?reload()?{
//?...
Spoon?spoon?=?getSpoon();
//?...
}
}
理想情況下,你在下一次閱讀這段代碼的時候,你會處理這個“空勺子”(null?spoon)的問題。但如果你過了很久才回來看這段代碼,你還會記得在這個類里還有一些工作要做嗎?當然,你可以在你的源碼里全局搜索TODO,甚至你的集成開發(fā)環(huán)境有一個內(nèi)建的TODO列表支持。但如果你想把任務所在的類和方法也標注出來的話,XDoclet可以是另一種選擇。XDoclet可以為你的項目生成一個TODO報表。
2.1.2?添加XDoclet標簽
為了把你的TODO項目轉(zhuǎn)換成另一種更加正式的格式,你需要對代碼進行一些細微的改動。如下所示:
public?class?Matrix?{
/**?@todo?需要處理當沒有勺子的情況?*/
public?void?reload()?{
//?...
}
}
這里加入了一個XDoclet需要的類javadoc標簽。XDoclet會使用這些標簽標記的信息,以及在這種情況下標簽所處的類和方法,來生成TODO報表。
2.1.3?與Ant集成
要生成TODO報表,你需要確保在你的機器上正確安裝了XDoclet。
在Ant任務里,最少要包含一個目標(例如init目標)定義<documentdoclet>任務,這是一個Ant自定義任務,例如:
<taskdef?name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classname=”xdoclet.lib.path”?/>
這個<documentdoclet>任務是XDoclet核心代碼生成應用程序中的一個。
現(xiàn)在,你可以在Ant構(gòu)建文件中加入一個todo目標調(diào)用這個任務來生成TODO報表,如:
<target?name=”todo”?depends=”init”>
<documentdoclet?destdir=”todo”>
<fileset?dir=”${dir.src}”>
<include?name=”**/*.java”?/>
</fileset>
<info/>
</documentdoclet>
</target>
<info>子任務會遍歷你的源文件,查找todo標簽,并在todo子目錄下生成HTML格式的TODO報表。
2.1.4?創(chuàng)建一個更加職業(yè)化的TODO報表
XDoclet生成的TODO報表可以有一個更加職業(yè)化的外表。報表會列出一個概覽,顯示在哪個包哪個類里有todo項(以及todo項的個數(shù))。Todo項可以跟在方法、類和域上,從報表上可以清楚的區(qū)別它們。類級別的todo項會標注class,方法級別的todo項會在方法簽名上標注M。構(gòu)造函數(shù)和域相關(guān)的todo項也會進行相似的標注。
這個任務看起來很簡單,但考慮到你所需要做的只是在注釋上添加一些格式化的@todo標簽,相對于那種只有人才可以理解的無格式的松散的注釋,這種標簽是機器可讀的,也更容易編程處理。生成的輸出也更容易閱讀并且更加的商業(yè)化。
2.2?任務和子任務
生成todo報表,只是XDoclet可以完成的事情當中的冰山一角。當初,XDoclet因為可以自動生成EJB繁雜的接口和布署描述文件而聲名鵲起。然而,現(xiàn)在的XDoclet已經(jīng)發(fā)展成了一個全功能的、面向?qū)傩缘拇a生成框架。J2EE代碼生成只是XDoclet的一個應用方面,它可以完成的任務已經(jīng)遠遠超越了J2EE和項目文檔的生成。
2.2.1?XDoclet?任務
到現(xiàn)在為止,我們一直在討論使用XDoclet生成代碼,但事實上,更確切的說法應該是,我們使用XDoclet的一個特定的任務來生成代碼,比如<ejbdoclet>。每一個XDoclet任務關(guān)注于一個特定的領(lǐng)域,并提供這個領(lǐng)域的豐富的代碼生成工具。
[定義:任務(Tasks)是XDoclet里可用的代碼生成應用程序的高層概念。]
在XDoclet里,目前已有如下所示的七個核心任務。
<ejbdoclet>:面向EJB領(lǐng)域,生成EJB、工具類和布署描述符。
<webdoclet>:面向Web開發(fā),生成serlvet、自定義標簽庫和web框架文件。
<hibernatedoclet>:Hibernate持續(xù),配置文件、Mbeans
<jdodoclet>:JDO,元數(shù)據(jù),vender?configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用戶自定義模板來生成代碼。
<documentdoclet>:生成項目文件(例如todo列報表)
這其中,<ejbdoclet>最常用,并且很多項目也僅僅使用XDoclet來進行EJB代碼生成。<webdoclet>是其次一個常用的代碼生成任務。當然,在一個項目中同時使用幾個XDoclet任務是可能的(并且也是推薦的),但在這些任務之間是完全獨立的,它們彼此之間并不能進行直接的交流。
2.2.2?XDoclet子任務
XDoclet的任務是領(lǐng)域相關(guān)的,而在某個特定領(lǐng)域的XDoclet任務,又由許許多多緊密耦合在一起的子任務組成的,這些子任務每個都僅僅執(zhí)行一個非常特定和簡單的代碼生成任務。
[定義:子任務(subtasks)是由任務提供的單目標的代碼生成過程]
任務提供子任務執(zhí)行時的上下文,并且把這些相關(guān)的子任務組織管理了起來。任務會依賴這些子任務來生成代碼。在一個任務當中調(diào)用多個子任務來協(xié)同完成各種各樣比較大型的代碼生成任務是非常常見的。比如,在開發(fā)EJB時,你可能想要為每一個bean生成一個home接口,一個remote接口以及ejb-jar.xml布署描述符文件。這就是在<ejbdoclet>任務的上下文環(huán)境中的三個獨立的代碼生成子任務。
子任務可以隨意的組合排列,以滿足項目代碼生成的需要。某個XDoclet任務包含的子任務經(jīng)常會共享功能和在源文件中使用相同的XDoclet標簽。這意味著當你開始一個任務的時候,你可以很容易的集成進一個相關(guān)的子任務,而不需要很大的改動。
子任務交互
讓我們以<ejbdoclet>任務為例,看一下相關(guān)的子任務之間是如何進行關(guān)聯(lián)的。假設你正在開發(fā)一個CMP(容器管理持久化)實體Bean。你想要使用一些<ejbdoclet>的子任務:
?<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
?<localhomeinterface>:生成local?home接口。
?<localinterface>:生成local接口。
在執(zhí)行如上子任務的時候,你需要標記出你的實體Bean的CMP域。當你發(fā)布你的bean的時候,你還需要在開發(fā)商相關(guān)的布署描述符中提供某個特定的關(guān)系數(shù)據(jù)庫中的特定表和列與你的CMP實體Bean的映射關(guān)系。XDoclet可以讓你在原先已存在的CMP?XDoclet屬性基礎(chǔ)上再加上一些關(guān)系映射屬性,然后,你就可以在任務中加入一個開發(fā)商相關(guān)的子任務(例如<jboss>或者<weblogic>)來生成布署描述符文件。XDoclet提供了幾乎所有的應用服務器的支持,你只需要一些初始化的小改動,就可以進行這些應用服務器相關(guān)的代碼生成了。
但那只是冰山一角。你還可以使用<entitycmp>子任務為為你的bean生成一個實體bean接口的實現(xiàn)子類。如果你使用<valueobject>子任務來為了你的bean生成值對象,<entityemp>子任務還會為你的值對象生成方法的實現(xiàn)代碼。
覺得不可思議了吧。可惜XDoclet沒有提供<cupofcoffee>子任務,要不然我們可以喝杯咖啡,休息一下啦。
這里不是想向你介紹<ejbdoclet>所有的子任務或者<ejbdoclet>可以完成的所有代碼生成功能,而僅僅是想向你展示一下任務的子任務之間是如何工作在一起的。一旦你開始并熟悉了一個XDoclet?子任務,熟悉另一個子任務會變得非常簡單-?那種每個子任務都是孤立的相比,使用這種可以相互協(xié)作的子任務,開發(fā)成本會顯著的降低,效果也更加的立竿見影。?
2.3?使用Ant執(zhí)行任務
XDoclet“嫁”給了Ant。XDoclet任務就是Ant的自定義任務,除此以外,沒有其他運行XDoclet任務的方法。所幸的是,Ant已經(jīng)成為了Java構(gòu)建工具事實上的標準,所以這不算什么限制。事實上,反過來,XDoclet與Ant的這種“親密”關(guān)系使得XDoclet可以參與到任何Ant構(gòu)建過程當中去。
2.3.1?聲明任務
XDoclet并沒有和Ant一起發(fā)布,所以如果你想要使用XDoclet的話,就需要單獨的下載和安裝。在使用任何一個XDoclet的任務之前,你首先需要在使用Ant的<taskdef>任務來聲明它。例如:
<taskdef?name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的話,你就會知道這段代碼是告訴Ant加載<ejbdoclet>的任務定義。當然,你也可以以你喜歡的任何方式來命名這個自定義任務,但最好還是遵守標準的命名規(guī)律以免發(fā)生混淆。classname和classpathref屬性告訴Ant到哪里去找實現(xiàn)這個自定義任務的XDoclet類。如果你想使用其他的XDoclet任務,就必須要類似這樣首先聲明這個任務。
一般共通的做法是,把所有需要使用的XDoclet任務都放在Ant的一個目標里聲明,這樣在其他的目標里如果需要使用這些任務,只要depends這個任務就可以了。你可能已經(jīng)在Ant的構(gòu)建文件里包含了init目標,這就是放置XDoclet任務聲明的好地方(當然如果你沒有,你也可以建一個)。下面的例子就是在一個init目標里加入了<ejbdoclet>和<webdoclet>的聲明:
<target?name=”init”>
<taskdef?name=”documentdoclet”
classname=”xdoclet.modules.doc.DocumentDocletTask”
classpathref=”xdoclet.lib.path”?/>
<taskdef?name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”?/>
<taskdef?name=”webdoclet”
classname=”xdoclet.modules.web.WebDocletTask”
classpathref=”xdoclet.lib.path”?/>
</target>
現(xiàn)在,任務聲明好了,XDoclet“整裝待發(fā)”。
2.3.2?使用任務
你可以在任何目標里使用聲明好的任務。在任務的上下文環(huán)境里,可以調(diào)動相關(guān)的子任務。讓我們看一個例子,這個例子調(diào)用了<ejbdoclet>任務。不要擔心看不懂語法的細節(jié),現(xiàn)在你只需要關(guān)心一些基礎(chǔ)概念就可以了。
<target?name=”generateEjb”?depends=”init”>
<ejbdoclet?destdir=”${gen.src.dir}”>
<fileset?dir=”${src.dir}”>
<include?name=”**/*Bean.java”/>
</fileset>
<deploymentdescriptor?destdir=”${ejb.deployment.dir}”/>
<homeinterface/>
<remoteinterface/>
<localinterface/>
<localhomeinterface/>
</ejbdoclet>
</target>
把任務想像成一個子程序運行時需要的一個配置環(huán)境(記住,子任務才是真正進行代碼生成工作的)。當調(diào)用一個子任務時,子任務從任務繼承上下文環(huán)境,當然,你也可以根據(jù)需要隨意的覆蓋這些值。在上面的例子里,因為<deploymentdescriptor>子任務生成的布署描述符文件和其他生成各種接口的子任務生成的Java源文件需要放在不同的位置,所以覆蓋了destdir的屬性值。布署描述符文件需要放在一個在打包EJB?JAR文件的時候可以容易包含進來的地方,而生成的Java代碼則需要放置在一個可以調(diào)用Java編譯器進行編譯的地方。需要這些子任務之間是緊密關(guān)聯(lián)的,但只要你需要,你可以有足夠的自主權(quán)控制任務的生成環(huán)境。
<fileset>屬性同樣被應用到所有的子任務。這是一個Ant的復雜類型(相對于文本和數(shù)值的簡單類型),所以以子元素的方式在任務中聲明。不要把它和子任務混為一談。當然,如果你想在某個子任務中另外指定一個不同的輸入文件集,你也可以在這個子任務中放置一個<fileset>子元素來覆蓋它。
子任務的可配置選項遠遠不止這些。我們會在下一章繼續(xù)介紹所有的任務和子任務,以及常用的配置選項。
2.4?用屬性標注你的代碼
可重用的代碼生成系統(tǒng)需要輸入來生成感興趣的輸出。一個解析器生成器也需要一個語言描述來解析生成解析器。一個商務對象代碼生成器需要領(lǐng)域模型來知道要生成哪些商務對象。XDoclet則需要Java源文件做為輸出來生成相關(guān)的類或者布署/配置文件。
然而,源文件可能并沒有提供代碼生成所需要的所有信息。考慮一個基于servlet的應用,當你想生成web.xml文件的時候,servlet源文件僅可以提供類名和適當?shù)膕ervlet接口方法。其他的信息比如URI?pattern映射、servlet需要的初始化參數(shù)等信息并沒有涵蓋。顯而易見,如果class并沒有提供這些信息給你,你就需要自己手動在web.xml文件時填寫這些信息。
XDoclet當然也不會知道這些信息。幸運的是,解決方法很簡單。如果所需信息在源文件時沒有提供,那就提供它,做法就是在源文件里加入一些XDoclet屬性。XDoclet解析源文件,提取這些屬性,并把它們傳遞給模板,模板使用這些信息生成代碼。
2.4.1?剖析屬性
XDoclet屬性其實就是javadoc的擴展。它們在外表上和使用上都有javadoc屬性一樣,可以放置在javadoc文檔注釋里。文檔注釋以/**開始,*/結(jié)尾。下面是一個簡單的例子:
/**
*?這是一段javadoc注釋。
*?注釋可以被分解成多行,每一行都以星號開始。
*/
在注釋里的所有文本都被視為javadoc注釋,并且都能夠被XDoclet訪問到。注釋塊一般都與Java源文件中的某個實體有關(guān),并緊跟在這個實體的前面。沒有緊跟實體的注釋塊將不會被處理。類(或者接口)可以有注釋塊,方法和域也可以有自己的注釋塊,比如:
/**
*?類注釋塊
*/
public?class?SomeClass?{
/**?域注釋塊?*/
private?int?id;
/**
*?構(gòu)造函數(shù)注釋塊
*/
public?SomeClass()?{
//?...
}
/**
?*?方法注釋塊
?*/
public?int?getId()?{
return?id;
}
}
注釋塊分成兩部分:描述部分和標簽部分。當遇到第一個javadoc標簽時,標簽部分開始。Javadoc標簽也分成兩部分:標簽名和標簽描述。標簽描述是可選的,并且可以多行。例如:
/**
*?這是描述部分
*?@tag1?標簽部分從這里開始
*?@tag2
*?@tag3?前面一個標簽沒有標簽描述。
*?這個標簽有多行標簽描述。
*/
XDoclet使用參數(shù)化標簽擴展了javadoc標簽。在XDoclet里,你可以在javadoc標簽的標簽描述部分加入name=”value”參數(shù)。這個微小的改動大大增強了javadoc標簽的表達能力,使得javadoc標簽可以用來描述復雜的元數(shù)據(jù)。下面的代碼顯示了使用XDoclet屬性描述實體Bean方法:
/**
*?@ejb.interface-method
*?@ejb.relation
*?name=”blog-entries”
*?role-name=”blog-has-entries”
*?@ejb.value-object
*?compose=”com.xdocletbook.blog.value.EntryValue”
*?compose-name=”Entry”
*?members=”com.xdocletbook.blog.interfaces.EntryLocal”
*?members-name=”Entries”
*?relation=”external”
*?type=”java.util.Set”
*/
public?abstract?Set?getEntries();
參數(shù)化標簽允許組合邏輯上相關(guān)聯(lián)的屬性。你可以加入描述這個類的元信息,使得這個類的信息足夠生成代碼。另外,程序員借由閱讀這樣的元信息,可以很快的理解這個類是如何使用的。(如果這個例子上的元信息你看不懂,不要擔心,在第4章里,我們會學到EJB相關(guān)的標簽以及它們的涵意。)
另外,請注意上面的例子中,所有的標簽名都以ejb開頭。XDoclet使用namespace.tagname的方式給標簽提供了一個命名空間。這樣做除了可以跟javadoc區(qū)別開來以外,還可以把任務相關(guān)的標簽組織起來,以免任務之間的標簽產(chǎn)生混淆。?
2.5?代碼生成模式
XDoclet是一種基于模板的代碼生成引擎。從高層視圖上來看,輸出文件其實就是由解析執(zhí)行各式各樣的模板生成出來的。如果你理解了模板以及它所執(zhí)行的上下文環(huán)境,就可以確切的認識到,XDoclet可以生成什么,不可以生成什么。如果你正在評估XDoclet平臺,理解這些概念是非常重要的。要不然,你可能會錯過XDoclet的許多強大的功能,也可能會被XDoclet的一些限制感到迷惑。
XDoclet運行在在Ant構(gòu)建文件環(huán)境中,它提供了Ant自定義任務和子任務來與XDoclet引擎交互。任務是子任務的容器,子任務負責執(zhí)行代碼生成。子任務調(diào)用模板。模板提供了你將生成代碼的餅干模子。XDoclet解析輸入的源文件,提取出源文件中的XDoclet屬性元數(shù)據(jù),再把這些數(shù)據(jù)提供給模板,驅(qū)動模板執(zhí)行。除此之外,模板還可以提供合并點(merge?points),允許用戶插入一些模板片斷(合并文件merge?files)來根據(jù)需要定制代碼生成。
2.5.1?模板基礎(chǔ)
XDoclet使用代碼模板來生成代碼。模板(template)是你想生成文件的原型。模板里使用一些XML標簽來指導模板引擎如何根據(jù)輸入類以及它們的元數(shù)據(jù)來調(diào)整代碼的生成。
[定義:模板(template)是生成代碼或描述文件的抽象模視圖。當模板被解析的時候,指定的細節(jié)信息會被填入。]
模板一般情況下會有一個執(zhí)行環(huán)境。模板可能應用在一個類環(huán)境(轉(zhuǎn)換生成transform?generation),也有可能應用在一個全局環(huán)境(聚集生成aggregate?generation)。轉(zhuǎn)換生成和聚集生成是XDoclet的兩種類型的任務模式,理解它們之間的區(qū)別對于理解XDoclet是非常重要的。
當你使用XDoclet生成布置描述符文件時,你使用的是聚集生成。布置描述符文件并不僅僅只與一個類相關(guān),相反,它需要從多個類里聚集信息到一個輸入文件。在這種生成模式里,解析一次模板只會生成一個輸出文件,不管有多少個輸入文件。
在轉(zhuǎn)換生成模式里,模板遇到每一個源文件就會解析一次,根據(jù)該文件類的上下文環(huán)境生成輸出。這種生成模式會為每一個輸入文件生成一個輸出文件。
轉(zhuǎn)換生成模式的一個很好的例子是生成EJB的local和remote接口。顯然,接口是和Bean類一一相關(guān)的。從每一個類里提取信息(類以及它的方法、域、接口以及XDoclet屬性等信息)轉(zhuǎn)換出接口。除此以外,不需要其他的信息。
從實現(xiàn)里提取出接口似乎有點反向。如果你手寫程序的話,一般來說會先定義一個接口,然后再寫一個類來關(guān)現(xiàn)它。但XDoclet做不到,XDoclet不可能幫你實現(xiàn)一個已有接口,因為它不可能幫你生成你的業(yè)務邏輯。當然,如果業(yè)務邏輯可以從接口本身得到(比如JavaBean的get/set訪問器)或者使用XDoclet屬性聲明好,那么生成業(yè)務邏輯代碼來實現(xiàn)一個接口也不是不可能。但一般情況下,這樣做不太現(xiàn)實。相比而言,提供一個實現(xiàn),并描述接口與這個實現(xiàn)之間的關(guān)聯(lián)就容易多了。
聚集生成和轉(zhuǎn)換生成主要區(qū)別在它們的環(huán)境信息上。即使一個代碼生成任務中生成一個Java文件,一般也不常用聚集生成,因為生成一個Java類還需要一些重要信息如類所處的包以及你想生成的類名,在這種環(huán)境下是無法提供的。如果一定要使用聚集生成的話,那就需要在另一個單獨的地方提供好配置信息了。
2.5.2?模板標簽
在還沒見到模板長啥樣子之前,我們已經(jīng)比較深入的認識它了。那模板文件究竟長啥樣子呢?它有點像JSP文件。它們都包含文件和XML標簽,生成輸出文件時XML標簽會被解析,然后生成文本并顯示在XML標簽所處的位置上。除了以XDt為命名空間打頭的XML標簽會被XDoclet引擎解析以外,其余的XML標簽XDoclet會忽略不管。下面的代碼片斷顯示了XDoclet模板的“經(jīng)典造型”:
public?class
<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
Extends?Observabe?{
static?<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance?=?null;
public?static?<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClassOf>
getInstance()?{
if?(_instance?==?null)?{
_instance?=
new?<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/>
</XDtClass:classOf>();
}
return?_instance;
}
}
研究一下這個模板,你會發(fā)現(xiàn),它生成的是一個類定義。這個類里定義了一個靜態(tài)變量instance,并且使用一個靜態(tài)方法來控制這個靜態(tài)文件的訪問。借助Java語法,你可以很容易的推斷出那些XDoclet模板標簽的目錄是生成類名,雖然對于這個標簽如何工作你還并不是很了解。
即使你從沒打算過要自己寫模板,但理解模板是如何被解析運行的還是很有必要的。遲早你會調(diào)用到一個運行失敗的XDoclet任務,沒有產(chǎn)生你所期望的輸出,那么最快捷的找出原因的方法就是直接檢查模板文件,看看是哪里出了問題。
讓我們看一下生成靜態(tài)域定義的片斷:
static?<XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance?=?null;
在XDoclet的眼里,這段模板代碼很簡單,那就是:
static?<tag/>?_instance?=?null;
XDoclet解析執(zhí)行標簽,如果有輸出的話,把輸入置回到文本里去。有些標簽會執(zhí)行一些運算,把輸出放回到一個流里。這樣的標簽稱之為內(nèi)容標簽(content?tags),因為它們產(chǎn)生內(nèi)容。
另一種類型的標簽稱之為BODY標簽。BODY標簽在開始和結(jié)束標簽之間存在文本。而BODY標簽強大就強大在這些文本自己也可以是一斷可以由外圍標簽解析的模板片斷。比如在上面的例子里,XDtClass:classOf標簽,它們的內(nèi)容就是模板片斷:
<XDtEjbFacade:remoteFacadeClass/>
classOf標簽解析這段模板,提取出全限制的內(nèi)容,然后剃除前面的包面,只輸出類名。BODY標簽并不總是會解析它的內(nèi)容,在做這件事之前,它們會事先檢查一些外部判斷條件(比如檢查檢查你正在生成的是一個接口還是一個類)。這里標標簽稱之為條件標簽(conditional?tags)。還有一些BODY標簽提供類似迭代的功能,它的內(nèi)容會被解析多次。比如一個標簽針對類里的每一個方法解析一次內(nèi)容。
XDoclet標簽提供了許多高層次的代碼生成功能,但是有時候,它們可能顯得不夠靈活,或者表達能力滿足不了你的需要。這時候,相對于另外開發(fā)一套通用功能的模板引擎相比,你可以選擇擴展XDoclet模板引擎。你可以使用更具表述能力、功能更加強大的Java平臺開發(fā)你自己的一套標簽。?
2.6?使用合并定制
代碼生成系統(tǒng)之所以使用的不多,主要原因就在于它們往往只能生成一些死板的、不夠靈活的代碼。大多數(shù)代碼生成系統(tǒng)不允許你改動它們生成的代碼;如果,如果這個系統(tǒng)不夠靈活,你所能做到的最好的擴展就是應用繼承擴展生成的代碼,或者使用一些共通的設計模式(比如Proxy和Adaptor)來滿足你的需要。無論如此,這都不是產(chǎn)生你想生成的代碼的好辦法。代碼生成器最好能做到所生成即所得WYGIWYG(what?you?generate?is?what?you?get),來取代你需要花費大量的時間來粉飾生成出來的并不滿足要求的代碼。所以,對于代碼生成器來說,支持靈活的定制,是生成能夠完全滿足要求的代碼的前提條件。
XDoclet通過合并點(merge?points)支持定制??合并點是在模板文件定義里允許運行時插入定制代碼的地方。有時候,合并點甚至可以影響到全局代碼的生成,不但允許你添加一些定制內(nèi)容,還可以從根本上改變將要生成出來的東西。
[定義:合并點(Merge?points)是模板預先定義的允許你在代碼生成的運行時加入定制內(nèi)容的擴展點]
讓我們研究一段從XDoclet源代碼里摘取出來的模板代碼。在為實體Bean生成主鍵的模板末尾,定義了這樣一個合并點:
<XDtMerge:merge?file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目錄下創(chuàng)建了一個名為entitypk-custom.xdt文件,那么這個模板文件的內(nèi)容將會在這個合并點被包含進來。你的定制可以執(zhí)行高層模板可以執(zhí)行的所有強大功能,可以進行所有模板可以進行的運算(包括定義自定義標簽,定義它們自己的合并點)。
上面的這種合并點,為所有的類環(huán)境使用了同一個文件。當然,也可以為每一個類環(huán)境使用不同的合并文件。如果你不想定制全部的類文件,或者你不想為了某些改動而重寫模板的時候,這會很有用。不管動機是什么,逐類的合并點很容易識別出來:他們會在名字里包含一個XDoclet的逐類標記{0}。這里有一個生成ejb-jar.xml文件里的安全角色引用的例子:
<XDtMerge:merge?file=”ejb-sec-rolerefs-{0}.xml”>
<XDtClass:forAllClassTags?tagName=”ejb:security-role-ref”>
<security-role-ref>
<role-name>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-name”/>
</role-name>
<role-link>
<XDtClass:classTagValue
tagName=”ejb:security-roleref”
paramName=”role-link”/>
</role-link>
</security-role-ref>
</XDtClass:forAllClassTags>
</XDtMerge:merge>
這段模板會遍歷工程里的所有Bean。對于每一個Bean,XDoclet先從Bean的文件名里提取出Bean名,然后替換{0},再根據(jù)替換后的文件名去尋找合并文件。例如,如果你有一個名為BlogFacadeBean的Bean,XDoclet會嘗試尋找一個名為ejb-src-rolerefs-BlogFacade.xml的合并文件。
如果找不到這個合并文件,則這個<merge>標簽的內(nèi)容模板會被解析。這意味著合并點不僅可以提供定制內(nèi)容,還可以在一個模板文件里定義一個替換點,當定制內(nèi)容不存在的時候使用替換點里的內(nèi)容。不是所有的XDoclet任務都提供了有替換內(nèi)容的合并點,一般來說,它們更傾向于只提供一個簡單的合并點,僅僅當合并文件存在的時候解析并導入合并文件的內(nèi)容。這取決于任務的開發(fā)者覺得哪種合并點更符合他的要求。
還有一點沒有介紹到的是XDoclet如何定位合并文件,每一個XDoclet任務或者子任務都會提供一個mergeDir屬性,這個屬性用于設置你存放合并文件的目錄。?
posted on 2006-11-05 08:26
xzc 閱讀(374)
評論(0) 編輯 收藏 所屬分類:
Xdoclet