©原作者:Rick Hightower
•XDoclet概要:
XDoclet 是一个通用的代码生成实用程序,是一个扩展的Javadoc Doclet引擎(现已与Javadoc Doclet独立),XDoclet是EJBDoclet的后继者,而EJBDoclet是由Rickard Oberg发起的。(http://xdoclet.sourceforge.net/xdoclet/index.html), 它允许您使用象 JavaDoc 标记之类的东西来向诸如类、方法和字段之类的语言特征添加元数据。随后,它利用这些额外的元数据来生成诸如部署描述符和源代码之类的相关文件。可以让你创 建自己的javadoc @tags进而利用XDoclet中的Templet enging基于这些@tags生成源代码或其他文件(例如xml的deployment descriptors)。
XDoclet 继承了 JavaDoc 引擎的思想,允许根据定制 JavaDoc 标记生成代码和其他文件。当然,XDoclet 也可以访问整个解析树。这样,它就可以访问类、类的包结构和类的方法。
| 人类发明了计算机来让它做那些枯燥无味的事情,而将自己解脱出来,去做有创造性的事情。使用 XDoclet 模板将使开发人员从单调无味的代码中解脱出来。 |
XDoclet 提供了自己的模板引擎。该模板引擎在概念上类似于 JavaServer Pages(JSP)技术。它实质上包含两类标记:块标记(block tag)和内容标记(content tag)。块标记控制如 Java 编程语言中的 if 和 for 语句之类的流。内容标记打印当前解析树上下文的片段,如类名称、方法名称和参数,等等。
•模板示例:
这里有一个简单的模板,它寻找所有的实体 Bean(EntityBean),然后打印出它们的类名称及其所有 cmp 字段的名称:
| <XDtClass:forAllClasses type="javax.ejb.EntityBean"> Classname=<XDtClass:className/> <XDtProperty:forAllPropertiesWithTag tagName="ejb.persistence"> CMP Field = <XDtMethod:propertyName/> </XDtProperty:forAllPropertiesWithTag> </XDtClass:forAllClasses> |
这个模板同时演示了块标记和内容标记。
—forAllClasses 是一个块标记示例,它遍历传递给模板引擎的所有类,这些类是通过调用 XDoclet 的 ant 构建文件中的 fileset 传递的。forAllClasses 使用 type 属性(type="javax.ejb.EntityBean")过滤掉不属于类型 javax.ejb.EntityBean 的那些类。
—className 是内容标记的一个示例,它打印出当前类的名称。
forAllProperitesWithTag 是另一个块标记示例。它遍历实现类中所有具有 XDoclet 标记 @ejb.persistence 的特性(这些特性与该 bean 的 cmp 字段相关)。最后,propertyName 是内容标记的另外一个示例,因为它在遍历过程中显示当前特性名称。
下面用黑色粗体字体显示了块标记,用红色粗体字体显示了内容标记。另外,这些块还用圆矩形框来演示其范围。
|
这个代码模板的输出如下:
| Classname=EmployeeBean CMP Field = id CMP Field = firstName CMP Field = lastName CMP Field = phone Classname=DeptBean CMP Field = id CMP Field = name |
要运行该模板,您的 ant 构建文件中将需要下列代码:
| <target name="templatedoclet" > <taskdef name="templatedoclet" classname="xdoclet.DocletTask" classpathref="xdocpath" /> <templatedoclet destdir="test"> <fileset dir="${src}"> <include name="**/*Bean.java"/> </fileset> <template templateFile="template/template.xdt" destinationfile="test.txt"/> </templatedoclet> </target> |
templatedoclet 任务用来执行模板。fileset 子元素用于指定:您只想要 src 目录中以“Bean.java”结尾的文件。模板子任务用于指定被使用的模板文件以及目标文件。
•XDoclet 体系结构:
XDoclet 由三个主要组件组成:
模块由几个部分组成:
|
—XJavaDoc 引擎:XJavaDoc 解析 Java 源文件,然后构建有关类和语言特征(包、方法和字段)以及元数据的信息树。XJavaDoc 引擎通过一个易于使用的 API 提供访问。该 API 提供了与带有一些额外特征的 JavaDoc API 相同的类信息,这些额外特征与存储及读取元数据以及其他结构相关联。XJavaDoc 增加了在运行时修改 JavaDoc 标记的能力。这样就可以推断元数据,并可以将其缺省值设为比较合理的值。
—XDoclet 引擎:XJavaDoc 引擎读取标记,这些标记组成了类的元数据和结构。XDoclet 引擎使用来自 XJavaDoc 引擎的信息,来生成支持文件(源代码和部署描述符)。XDoclet 提供了一个优秀的模板生成引擎,该引擎将模板转换成一个或多个支持文件。XDoclet 有一个模块装入程序,它动态地装入用 xdoclet.xml 文件(包含在模块的 jar 文件中)指定的 XDoclet 模块。
您无需创建模块就可创建模板。每个顶级 XDoclet Ant 任务都有执行任意模板的能力,以此替换随模块一起提供的模板。
—模块引擎:模块由任务、子任务、标记处理程序和模板组成。
|
子任务:子任务指定要调用的缺省模板,它允许您向该模板传递配置参数。在编写了一些模板之后,您将编写一个子任务。子任务的示例如下所示:
上面的 模板:XDoclet 模板生成部署描述符和源代码文件。实际上,XDoclet 模板可以生成任何类型的文件。正是这种能力为组件开发提供了帮助,生成了组件的各种配置文件和部署描述符。实际上,您甚至可以编写自己的组件框架,然后使 用 XDoclet 来生成另外的部署类型文件。 标记处理程序:标记处理程序对于 XDoclet 标记,就如同定制标记(Custom Tag)处理程序对于 JSP 定制标记。XDoclet 引擎根据标记的名称将标记映射到与该标记对应的标记处理程序。标记处理程序类必须生成 xdoclet.TagHandler 的子类。XDoclet 使用反射来根据标记名称调用标记处理程序的方法。因此,XDtProperty:forAllPropertiesWithTag 在 Property 标记处理程序中寻找方法 forAllPropertiesWithTag。 模板引擎通过在与标记一起提供的 xdoclet.xml 文件中查找标记处理程序,来了解要使用哪个标记处理程序。以下是核心 xdoclet.xml 文件中用于 Property 标记处理程序入口的代码片段:
当 XDoclet 碰到 XDtProperty:forAllPropertiesWithTag 标记时,它便在 以下是 Property 标记处理程序的部分清单:
|
•查找模板块和内容标记:
要开发自己的定制模板,最好的方法是参考 XDoclet 在其模块中提供的类似模板。您可以这样做:解压缩(unjar)那些模块,寻找以 xdt 结尾的文件,或者下载 XDoclet 源代码并寻找那些模板。此外,还有一种方法,借助类似于 JavaDoc 的文档来查找与 XDoclet 一起提供的所有可用的模板标记。
幸运的是,XDoclet 提供了整个模板语言的参考大全。可以在 [XDoclet Install Dir]\docs\templates\index.html 中找到该参考大全。它是您开发真正属于自己的定制模板的指南。
如果您熟悉 JavaDoc,那么该模板语言参考大全使用起来就很简单。XDoclet 标记处理程序对应于那些标记的名称空间,它们显示在左上面板中。特定处理程序的实际标记显示在左下方,而该标记的文档则显示在右边的面板中。如果您想查找 一个标记(例如,XDtClass:forAllClasses),那么请从名称空间剥离掉 Xdt(例如,XdtClass 变成 class),然后查找该标记(如,forAllClasses)。请参阅上图以便搞清楚如何查找 XDtClass:forAllClasses。
----使用Xdoclet生成web.xml文件
•第一步︰定義 Servlet 元素
第一步是在您的類別中把 servlet 元素定義為類別層級的 XDoclet JavaDoc 標示(如下所示)︰
| ... * @web.servlet name="BasicServlet" display-name="Basic Servlet" load-on-startup="1" ... */ public class BasicServlet extends HttpServlet { |
這段程式碼生成 web.xml 中 servlet 元素和子元素(如下所示)︰
| <servlet> <servlet-name>BasicServlet</servlet-name> <display-name>Basic Servlet</display-name> <servlet-class>rickhightower.servlet.BasicServlet</servlet-class> ... <load-on-startup>1</load-on-startup> </servlet> |
因 為 XDoclet 工作的工作原理類似於 JavaDoc API,所以它像 JavaDoc API 為 JavaDoc 獲取完整的類別名稱那樣獲取 servlet 的完整的類別名稱。這不只有減少了輸入,還減少了犯錯誤的可能。後來,當您重構和決定變更類別名稱或 package 結構的時候,您不必手動地變更所有的部署描述子。
•第二步︰定義 Servlet 的初始參數
在使用 Servlet 元素定義了 servlet 之後,您可以定義映射和初始參數。servlet-init-param 被定義在 JavaDoc 註釋中(如下所示)︰
| ... * @web.servlet-init-param name="hi" value="Ant is cool!" * @web.servlet-init-param name="bye" value="XDoc Rocks!" ... */ public class BasicServlet extends HttpServlet { |
這些參數將生成部署描述子中的以下 >init-param>︰
| <servlet> <servlet-name>BasicServlet</servlet-name> ... <init-param> <param-name>hi</param-name> <param-value>Ant is cool!</param-value> </init-param> <init-param> <param-name>bye</param-name> <param-value>XDoc Rocks!</param-value> </init-param> ... </servlet> |
•第三步︰綜合應用 Ant 和 XDoclet 來設定元件
您一般不願把初始化參數硬編碼到原始碼中。有關使用 <init-param> 的全部概念是使 Web 元件可被應用程式組譯器(application assembler)定製為 J2EE 應用程式。
更好的方法是把初始參數設為指向類似 @bye@ 和 @hi@ 中的標示,然後使用啟用了過濾的 Ant 複製來為正確的應用程式傳遞正確的標示值。這裡假定您使用 Ant 來建置專案。
您可以使用 Ant 過濾來把設定文件中的標示置換成它們在部署環境中的正確的值。過濾器是支援設定多個應用程式的 J2EE 元件的另一種方式。以下是根據條件來設定 web.xml 的範例 Ant 腳本︰
| <project name="filtering" default="run"> <target name="spanishSetup" if="spanish"> <filter token="bye" value="adios"/> <filter token="hi" value="hola"/> </target> <target name="englishSetup" unless="spanish"> <filter token="bye" value="goodbye"/> <filter token="hi" value="hello"/> </target> <target name="setup" depends="spanishSetup,englishSetup"/> <target name="run" depends="setup"> <copy todir="${workspace}/WEB-INF" filtering="true"> <fileset dir="./WEB-INF"/> </copy> </target> </project> |
在上面的 Ant 範例中,englishSetup 目標中的過濾器把 bye 標示設為 goodbye,而 spanishSetup 目標中的過濾器把 bye 標示設為 adios。
稍後,當腳本使用啟用了過濾的複製工作時,它把過濾器應用於複製任務指定的文件集中的所有的文件。若 spanish 屬性被設定,則啟用了過濾的複製任務把所有的字串 @bye@ 取代成為 adios,若 spanish 屬性沒有被設定,則 goodbye。
更容易的方法……
雖然這是解決這個問題的一個方法,但是還有更容易的方法。Ant 屬性可被用來設定 XDoclet 中的每個屬性值。因為您用 XDoclet Ant 任務來生成相關的文件,所以 XDoclet 可存取所有的 ant 屬性。因此,您可以這樣來設定值︰
| ... * @web.servlet-init-param name="hi" * value="${basic.servlet.hi}" * * @web.servlet-init-param name="bye" * value="${basic.servlet.bye}" ... */ public class BasicServlet extends HttpServlet { |
然後,當您生成 web.xml 時,hi 和 bye 初始化參數被設定成 Ant 建置腳本中設定的 basic.servlet.hi 和 basic.servlet.bye 屬性的目前值。
•第四步︰定義 Servlet 映射
XDoclet JavaDoc 標示也可被用來定義 servlet 映射(如下所示)︰
| * @web.servlet-mapping url-pattern="/Basic/*" * @web.servlet-mapping url-pattern="*.Basic" * @web.servlet-mapping url-pattern="/BasicServlet" ... */ public class BasicServlet extends HttpServlet { |
這將生成 web.xml 中的以下項目︰
| <servlet-mapping> <servlet-name>BasicServlet</servlet-name> <url-pattern>/Basic/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>BasicServlet</servlet-name> <url-pattern>*.Basic</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>BasicServlet</servlet-name> <url-pattern>/BasicServlet</url-pattern> </servlet-mapping> |
•第五步︰定義 J2EE 資源
除了上述內容外,您可以在 web.xml 中為類似 JDBC 資料源的資源定義資源參照,甚至定義 ejb 參照。Java 文件在類別層級包括這些 XDoclet JavaDoc 樣式的標示(如下所示)︰
| /** ... * @web.resource-ref description="JDBC resource" * name="jdbc/mydb" * type="javax.sql.DataSource" * auth="Container" ... */ public class BasicServlet extends HttpServlet { |
以上程式碼生成 web.xml 中如下元素︰
| ... <resource-ref> <description>JDBC resource</description> <res-ref-name>jdbc/mydb</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> |
•如何生成它? 以下是 build.properties 文件的清單︰####### Change These for your environment############## |
--使用 XDoclet 的 webdoclet 任務來建立 custom tag TLD
•custom tag 的類別層級 JavaDoc 標示
與前面相似,映射是很自然的。第一步是定義 jsp 標示(使用 jsp.tag 並傳遞 custom tag 的名稱),如下所示︰
| * @jsp.tag name="BasicTag" |
這段程式碼生成 TLD 文件中的以下程式碼︰
| <tag> <name>BasicTag</name> <tag-class>tomcatbook.customtag.BasicTag</tag-class> ... </tag> |
XDoclet 使用 JavaDoc API 來獲取 custom tag 處理程式的完整的類別名稱。接著,您定義使用 custom tag 的 JSP 頁面可用的任何變數。在這個範例中,您定義三個變數。其中一個變數被用於開始標示後,另一個只有用於結束標示後,還有一個只有用於主體中,如下所示︰
| * @jsp.variable name-given="currentIter" * class="java.lang.Integer" scope="NESTED" * @jsp.variable name-given="atBegin" * class="java.lang.Integer" scope="AT_BEGIN" * @jsp.variable name-given="atEnd" * class="java.lang.Integer" scope="AT_END" |
XDoclet 的另一個好處是它使這個 custom tag 構成的所有東西被儲存在一個文件中。此外,它也很適合於記錄構成這個 custom tag 的東西。想想如果不用 XDoclet,您必須在長長的 TLD 文件中尋找正確的項目(struts-html.tld 文件長達 3000 行﹗)才能瞭解這個標示定義的變數。
這段程式碼在 TLD 文件的基本標示定義中生成以下程式碼︰
| </p><p> <variable> <name-given>currentIter</name-given> <variable-class>java.lang.Integer</variable-class> <scope>NESTED</scope> </variable> <variable> <name-given>atBegin</name-given> <variable-class>java.lang.Integer</variable-class> <scope>AT_BEGIN</scope> </variable> <variable> <name-given>atEnd</name-given> <variable-class>java.lang.Integer</variable-class> <scope>AT_END</scope> </variable> |
•custom tag 的方法層級的 JavaDoc 標示
custom tag 使用方法層級的 JavaDoc 標示來為這個範例中的三個屬性(includeBody、includePage 和 iterate)定義 custom tag 屬性︰
| /** Getter for property includePage. * @return Value of property includePage. * @jsp.attribute required="true" * rtexprvalue="true" * description="The includePage attribute" */ public boolean isIncludePage() { return this.includePage; } ... /** Getter for property includeBody. * @return Value of property includeBody. * @jsp:attribute required="true" * rtexprvalue="true" * description="The includeBody attribute" */ public boolean isIncludeBody() { return this.includeBody; } ... /** Getter for property iterate. * @return Value of property iterate. * @jsp:attribute required="true" * rtexprvalue="true" * description="The iterate attribute" */ public int getIterate() { return this.iterate; } |
請注意,JavaDoc 標示 jsp.attribute 被用來把這個屬性(property)定義為屬性(attribute)。這段程式碼生成 TLD 文件的定義中的以下程式碼︰
設想一下重構和變更對應於屬性的 getter 和 setter 方法的名稱。如果沒有 XDoclet,那麼您不得不在 TLD 文件中搜尋。
•生成脚本:
| <webdoclet destdir="${dest}"> <fileset dir="${src}"> <include name="**/*Servlet.java" /> <include name="**/*Tag.java" /> </fileset> <deploymentdescriptor servletspec="2.3" destdir="${WEBINF}" > <taglib uri="mytaglib" location="WEB-INF/tlds/mytaglib.tld" /> </deploymentdescriptor> <jsptaglib jspversion="1.2" destdir="${WEBINF}/tlds" shortname="basic" filename="mytaglib.tld"/> </webdoclet> |
以上程式碼在 web.xml 中生成以下項目,如下所示︰
<taglib> <taglib-uri>mytaglib</taglib-uri> <taglib-location>WEB-INF/tlds/mytaglib.tld</taglib-location> </taglib>
--使用XDoclet生成Hibernate映射文件及DDL和数据库表
使用Hibernate,对实体类,需要Map文件,XDoclet在写类时加入类似Javadoc的@hibernate.xxx 指示,然后用XDoclet自动生成Map 文件,利用MAP文件可以生成建表脚本,使写程序更加方便。
XDoclet需要有相应的jar文件支持,不过,因为XDoclet是在Ant运行时使用的,所以XDoclet的jar可以不放在webapps\..\WEB-INF\lib下,可另外放一个目录,我们放在lib-xdoc下:
| |
我们建好src、lib、db、lib-xdoc等目录,在lib下放:
| |
建build.xml:
| <?xml version="1.0" encoding="UTF-8"?> <project name="Test" default="compile" basedir="."> <property name="classes" location="./classes"/> <property name="src" value="src" /> <property name="db" value="db" /> <property name="lib" value="lib" /> <property name="lib-xdoc" value="lib-xdoc" /> <property name="mysql.batch" value="${base}/db/schema.bat"/> <property name="mysql.schema" value="${base}/db/mysql.sql"/> <property name="build" value="classes" /> <path id="myclasspath"> <fileset dir="${lib}"> <include name="*.jar"/> </fileset> <fileset dir="${lib-xdoc}"> <include name="*.jar"/> </fileset> <pathelement location="${build}"/> </path> <target name="init"> <mkdir dir="${classes}"/> </target> <target name="compile" depends="init"> <javac executable="jikes" classpathref="myclasspath" srcdir="${src}" destdir="${classes}"/> </target> <target name="clean"> <delete dir="${classes}"/> </target> <target name="xdoc" description="Generates Hibernate class descriptor files."> <taskdef name="hibernatedoclet" classname="xdoclet.modules.hibernate.HibernateDocletTask"> <classpath refid="myclasspath"/> </taskdef> <hibernatedoclet destdir="${classes}" excludedtags="@version,@author,@todo" force="true" verbose="true"> <fileset dir="${src}"> <include name="**/*.java"/> </fileset> <hibernate version="2.0"/> </hibernatedoclet> </target> <target name="generatorDDL" description="Generates DB DLL files." depends="xdoc"> <echo message="database.." /> <java classname="net.sf.hibernate.tool.hbm2ddl.SchemaExport" fork="true"> <classpath refid="myclasspath"/> <arg value="--text"/> <arg value="--output=db/schema.sql"/> <arg value="--properties=src/hibernate.properties"/> <arg value="--delimiter=;"/> <arg value="classes/com/zdon/hibernate/*.xml"/> </java> <echo message="create database schema..." /> <exec executable="${mysql.batch}"> <arg file="${mysql.schema}" /> </exec> </target> <!-- Other target omited --> </project> |
建几个类:
package com.zdon.hibernate; import net.sf.hibernate.Validatable;
|
package com.zdon.hibernate; import java.util.*; |
package com.zdon.hibernate; import java.util.*; |
package com.zdon.hibernate; import java.util.*; |
自动生成的MAP文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC <hibernate-mapping> <id <property <many-to-one <set <key <one-to-many <!-- </class> </hibernate-mapping> |
自动生成的DDL脚本(这是Oracle的):
| alter table test_user_position drop constraint FKF1F5A230F73AEE0F; alter table test_user_position drop constraint FKF1F5A230F7CEDBF1; alter table test_org drop constraint FKBBAA89B7C4AB08AA; drop table test_persistent cascade constraints; drop table test_position cascade constraints; drop table test_user cascade constraints; drop table test_user_position cascade constraints; drop table test_org cascade constraints; create table test_persistent (id NUMBER(10,0) not null, primary key (id)); create table test_position (id NUMBER(10,0) not null, primary key (id)); create table test_user (id NUMBER(10,0) not null, primary key (id)); create table test_user_position (position_id NUMBER(10,0) not null, user_id NUMBER(10,0) not null, primary key (user_id, position_id)); create table test_org (id NUMBER(10,0) not null, name VARCHAR2(60), parent NUMBER(10,0), primary key (id)); alter table test_user_position add constraint FKF1F5A230F73AEE0F foreign key (user_id) references test_user; alter table test_user_position add constraint FKF1F5A230F7CEDBF1 foreign key (position_id) references test_position; alter table test_org add constraint FKBBAA89B7C4AB08AA foreign key (parent) references test_org; |

Leave a comment