转自:http://www.ibm.com/developerworks/cn/java/j-xdoclet/#genjavadoc
简介: 开放源代码的 XDoclet 代码生成引擎,是许多领先的 Java 框架不可缺少的组成部分,常常被用作面向属性的编程和持续集成的引擎。但是 XDoclet 还有一些不太惹人注目的地方:对初级开发人员来说,它太难掌握、太难精通。在这篇文章中,流行作者 Sing Li 以 XDoclet 为对象,揭示了其内部简单却优雅的设计,使您能够理解这项技术,并将它应用在实践当中。
XDoclet 能够很容易成为您的 Java 编程工具箱中的一个更加通用的跨技术代码生成工具。不幸的是,开发人员经常忽视 XDoclet 的一般用途,只有将它捆绑在大型开发框架或者 IDE 中,作为其中的一个隐藏元素时,才会用到它。人们常常认为很难将 XDoclet 应用在定制解决方案上。这篇文章的目的就是要消除这个迷惑,把 XDoclet 从常见的复杂陷阱中解脱出来,并向您展示了如何能够利用这个代码生成引擎。
我会用一个实际的例子演示 XDoclet 的用途,该例子将接收一个 POJO(plain old Java object),并用 XDoclet 生成完整 Web 应用程序的全部文件,这些文件是把数据输入关系数据库所必需的。该示例使用了 XDoclet 的 自定义模板代码生成功能,以及它对 Hibernate 对象关系映射工具、Struct Web 应用程序框架和应用程序服务器的内部支持。
XDoclet 的核心功能是根据以下组合来生成代码的(或者生成其他配置 / 数据文件):
- 进行特殊标记的 Java 源文件。
- 预先定义的模板。
与其他基于模板的代码生成技术(例如 Velocity;请参阅 参考资料)相比,XDoclet 具有以下独特优势:
- XDoclet 与 Apache Ant紧密集成,从而提供了高度自动化的操作。
- 把控制代码生成和模板处理的 XDoclet 标签作为内联注释嵌入到 Java 源代码文件中。这消除了同步多个相关文件和控制文件的需要。
- XDoclet 的内置 Java 解析器使用它对 Java 代码结构的深入理解,为输入的 Java 代码建立内部 结构模型。该结构模型又经常被叫作元数据(metadata),因为它包含与关联代码有关的数据。
- XDoclet 的模板生成逻辑拥有对输入 Java 代码的内部结构模型的完全访问权。
接下来,我将进一步研究 XDoclet 是如何工作的,以帮助您理解这些特性。
图 1 显示了 XDoclet 要求的输入和生成的输出。
图 1. XDoclet 黑盒子
您可以看到,包含嵌入式 XDoclet 标签的 Java 源代码是系统的输入。在 Apache Ant 的驱动下,XDoclet 处理输入的代码,生成的输出文本文件可以是 Java 源代码、HTML 页面、XML 文件等。为了处理输入,XDoclet 需要使用模板(保存在 .xdt 文件中)和标签处理器(用 Java 编码)。XDoclet 把模板和标签处理器打包成“模块”,不同的“模块”处理不同的问题域。
XDoclet 对包含嵌入式 XDoclet 标签的输入 Java 源代码进行解析,并为代码建立非常详细的结构模型。结构模型中的每个元素都代表源代码中的一个 Java 结构。图 2 显示的结构模型,揭示了 XDoclet 跟踪的代码构造和关系。
图 2. XDoclet 的解析的 Java 源代码的内部结构模型
图 2 中的结构模型跟踪类、接口、方法之类的代码构造(模型元素)。该模型还跟踪元素之间的关系,例如继承和接口实现。以内联注释的形式嵌入在源代码中的 XDoclet 标签被解析为模型元素的属性,并被跟踪。
图 3 显示了 XDoclet 的内部结构,揭示了使其运行的功能块。
如图 3 所示,Apache Ant 在运行的时候控制着 XDoclet 的配置和操作。XDoclet 解析输入的 Java 源代码,并在内存中生成结构模型。模板引擎通过处理一组模板和标签处理器,生成输出文件。模板和标签处理器可以是内置的,也可以是定制的。在代码生成期间,模板和标签处理器拥有对结构模型的完全访问。
XDoclet 实质上就是一个通用的 Javadoc 引擎(请参阅侧栏, 通用的 Javadoc 引擎)。那么,是什么让它看起来这么复杂呢?答案在于:XDoclet 几乎从未被单独讨论过,而总是藏在其他许多复杂的技术中。图 4 显示了了围绕在 XDoclet 周围的复杂性迷雾(请参阅侧栏为什么 XDoclet 看起来比实际的要复杂得多)。
图 4. XDoclet 的复杂耦合
在图 4 中,您可以看到 XDoclet 与以下内容是紧密相关的:
- Apache Ant,它控制着 XDoclet 的操作。XDoclet 是作为一组 Ant 任务存在的,没有 Ant 则不能执行。
- 与生成文件关联的具体问题领域的一些细节。
XDoclet 本身却是惊人地简单,正如下面示例中的工作代码所示的那样。
现在,您可以通过研究我向您提供的数据入口应用程序示例,来观察 XDoclet 的实际工作。(要下载这个示例中使用的 Java 代码、XDoclet 模板和 Ant 脚本,请单击本文顶部或底部的Code图标,或者请参阅 下载部分。)我们将从检查清单 1 所示的 Java 代码开始,这部分代码表示了一个客户的地址。该地址被编码成 JavaBean 组件,其中的 XDoclet 标签是以黑体字形式显示的:
清单 1. 用 XDoclet 标签标记的 AddressBean.java 源文件
package com.ibm.dw.beans; import java.io.Serializable; /** * @dw.genStrutsAction action="/addAddress.do" * @hibernate.class table="ADDRESS" */ public class AddressBean implements Serializable { private String streetNumber = ""; private String street = ""; private String city = ""; private String country = ""; private String postalCode = ""; private long id = 0; public AddressBean() { } /** * @dw.genStruts formlabel="Street Number" * @hibernate.property length="10" */ public String getStreetNumber() { return streetNumber; } public void setStreetNumber(String inpStreetNumber) { streetNumber = inpStreetNumber; } /** * @dw.genStruts formlabel="Street" * @hibernate.property length="40" */ public String getStreet() { return street; } public void setStreet(String inpStreet) { street = inpStreet; } ...... more Address bean properties ...... /** * @hibernate.id generator-class="native" */ public long getId( ) { return id; } public void setId(long inId) { id = inId; } } |
在清单 1 中,需要注意的是,要把 XDoclet 标签嵌入到注释中,紧放在相关代码元素(例如字段、方法、接口或类)的前面。在解析源代码时,XDoclet 会为每个标签建立一个属性,并将该属性附加到结构模型的代码元素上。现在,请注意@dw.genStruts
标签,因为这是在本例中将用到的第一个模板。
对于本例,您需要生成新的 Java 类的代码 —— 一个 Struts 表单 bean。Struts 会用这个 bean 保存并传输用户输入。bean 必须以 bean 属性的形式包含所有数据字段,而且它必须是org.apache.struts.action.ActionForm
的子类。
为了生成表单 bean 的代码,您要根据清单 2 所示的伪代码生成 XDoclet 模板。括号中的黑体字代表控制流逻辑和您要进行替换的文本。请注意模板是如何从已解析的 Java 源代码文件的结构模型中提取信息的:
清单 2. 建立 AddressBeanForm.java Struts 表单 bean 代码的伪代码模板
package {package name of source class}; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.upload.FormFile; /** * Form Bean class for {name of source class};. * * @struts.form name=" {name of source class}Form" */ public class {name of source class}Form extends ActionForm { {loop through all the methods in the source class} {if the method is a JavaBean "getter" method} {if the method has been marked with the @dw.genStruts tag } private {return type of method} {name of the JavaBean property}; public {return type of method} {name of the getter method for this property}(){ return {name of JavaBean property}; } public void {name of the setter method for this property}( {return type of method}value) { {name of the JavaBean property}= value; } {end of if @dw.genStruts} {end of if JavaBean getter} {end of loop} |
在清单 2 的循环中的代码生成了一个字段声明和一个访问器方法,还为输入源代码中每个用 @dw.genStruts
标记的访问器方法生成了一个设置器方法。 清单 2 使用了易于理解的伪代码来表示模板替换标签。实际的 XDoclet 模板标签则相当繁琐。清单 3 显示了 genformbean.xdt 模板(所有的 XDoclet 模板都保存在 .xdt 文件中)。我已经用黑体字强调了 XDoclet 模板标签,以方便在伪代码中对其进行引用。
清单 3. 建立 Structs 表单 bean Java 代码的实际 XDoclet 模板代码
package <XDtPackage:packageName/>; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.upload.FormFile; /** * Form Bean class for <XDtClass:className/>. * * @struts.form name=" <XDtClass:className/>Form" */ public class <XDtClass:className/>Form extends ActionForm { <XDtMethod:forAllMethods> <XDtMethod:ifIsGetter> <XDtMethod:ifHasMethodTag tagName="dw.genStruts"> private <XDtMethod:methodType/> <XDtMethod:propertyName/>; public <XDtMethod:methodType/> <XDtMethod:getterMethod/>(){ return <XDtMethod:propertyName/>; } public void <XDtMethod:setterMethod/>( <XDtMethod:methodType/>value) { <XDtMethod:propertyName/>= value; } </XDtMethod:ifHasMethodTag> </XDtMethod:ifIsGetter> </XDtMethod:forAllMethods> |
您可以参考 XDoclet 的“模板语言”文档,查找 XDoclet 所有可用标签的列表(请参阅 参考资料)。
要运行用于 AddressBean.java 源文件的模板,请使用以下 Ant 命令行:
ant -Dbase.class.java=Address genstruts |
这个命令可以执行定制 Ant 目标(请参阅侧栏 熟悉 Ant 脚本编写)来处理 genbeanform.xdt 模板。XDoclet 提供的 Ant 任务叫作xdoclet.DocletTask
,它被用来运行模板文件。如果您对 Ant 的细节感兴趣,请参阅示例代码中的 build.xml 文件,以了解更多信息。
在 XDoclet 处理模板的时候,它在名为 generated的子目录下生成一个 AddressBeanForm.java 文件。清单 4 显示了该文件,它包含模板处理期间替换的所有文本:
清单 4. XDoclet 生成的包含 Struts 表单 bean 的 Java 源代码
package com.ibm.dw.beans; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.upload.FormFile; /** * Form Bean class for AddressBean. * * @struts.form name=" AddressBeanForm" */ public class AddressBeanForm extends ActionForm { private java.lang.String streetNumber; public java.lang.String getStreetNumber(){ return streetNumber; } public void setStreetNumber( java.lang.Stringvalue) { streetNumber= value; } private java.lang.String street; public java.lang.String getStreet(){ return street; } public void setStreet( java.lang.Stringvalue) { street= value; } ...... more bean properties ..... } |
您可以用相同的 AddressBean.java 源文件,但是用 genformjsp.xdt 模板生成数据入口表单 JSP 页面。清单 5 显示了 genformjsp.xdt:
清单 5. 使用 Struts 标签库生成 JSP 页面来显示 HTML 表单的 XDoclet 模板
<%@ page language="java" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:html> <head> </head> <body bgcolor="white"> <html:errors/> <html:form action=" <XDtClass:classTagValue tagName='dw.genStrutsAction' paramName='action' />>" <table border="0" width="100%"> <XDtMethod:forAllMethods> <XDtMethod:ifIsGetter> <XDtMethod:ifHasMethodTag tagName="dw.genStruts" > <tr> <th align="right"> <XDtMethod:methodTagValue tagName="dw.genStruts" paramName="formlabel"/> </th> <td align="left"> <html:text property=" <XDtMethod:propertyName/>" size=" <XDtMethod:ifHasMethodTag tagName="hibernate.property" > <XDtMethod:methodTagValue tagName="hibernate.property" paramName="length"/> </XDtMethod:ifHasMethodTag>"/> </td> </tr> </XDtMethod:ifHasMethodTag> </XDtMethod:ifIsGetter> </XDtMethod:forAllMethods> <tr> <td align="right"> <html:submit> Submit </html:submit> </td> <td align="left"> <html:reset> Reset </html:reset> </td> </tr> </table> </html:form> </body> </html:html> |
请注意,代码中用 <XDt:methodTagValue>
取得原始 AddressBean.java 代码中在 XDoclet 标签中指定的属性值。
当您执行 genstruts
Ant 目标的时候,也会处理清单 5 显示的 genformjsp.xdt 模板。您可以在 generated子目录中找到生成的 AddressBeanForm.jsp 文件,检查文件内容,可以看到要对模板进行的替换。
您可以用 XDoclet 生成任意基于文本的输出。我向您展示的例子使用 XDoclet 生成了 Java 代码、JSP 页面、XML 文件、配置文件以及其他更多输出。它从一个简单的用 XDoclet 进行标记的 Java 源文件 AddressBean.java,建立了一个完整的数据入口 Web 应用程序。为了做到这一点,它执行了 XDoclet 的内置模板(位于 JAR 文件中,称为模块),从而生成:
- Struts 配置和支持文件。
- Hibernate 配置和支持文件。
- Web 应用程序的部署描述符(web.xml)。
表 1 显示了为示例应用程序生成的所有文件(通常称为 工件(artifact)):
表 1. XDoclet 为 AddressBean.java 生成的工件
生成的工件 | 说明 | 位置 |
AddressBeanForm.java | Java 源文件,包含表单 bean 类,在 Struts 的表单处理中使用 | generated目录 |
AddressBeanForm.jsp | JSP 表单,用 Struts 标签库接受用户地址输入 | jsp目录 |
AddressBeanAction.java | Struts 动作类,接受输入值,用 Hibernate 把值保存到关系数据库 | generated目录 |
AddressBean.hbm.xml | Hibernate 映射文件,在 AddressBean Java 对象和数据库的关系型 ADDRESS 表之间进行映射 | web/classes目录 |
dwschema.sql | RDBMS 表的架构,用来对 AddressBean 对象的实例进行持久化 | sql目录 |
hibernate.cfg.xml | Hibernate 运行时的配置文件 | web/classes目录 |
web.xml | 生成的 Web 应用程序的部署描述符 | web目录 |
struts-config.xml | Struts 框架的配置文件 | web目录 |
在这篇文章中,您详细了解了表 1 中所列的两个工件中的第一个工件的生成,深入了解了生成它们的模板。工件 AdddressBeanAction.java 则用类似的方法,利用叫作 genaction.xdt 的模板生成。XDoclet 具有内置模板和标签处理器,可以生成表 1 中的其他工件。
表 2 列出了每个生成的工件对应的 Ant 目标和 Ant 任务。您可以执行表格中的每个 Ant 目标,生成对应的工件。所有这些生成的工件,再加上原始和 AddressBean.java,共同构成了示例 Web 应用程序。您还会发现叫作all
的默认 Ant 目标,它会为您做任何事,包括为应用程序建立 WAR(可以部署的 Web 归档)。在进行处理之前,一定要阅读代码发布包中的 README.txt 文件。
表 2. 对应于生成工件的 Ant 目录和 Ant 任务
Ant 目标 | Ant 任务 | 工件 |
genstruts | xdoclet.DocletTask | AddressBeanForm.java |
genstruts | xdoclet.DocletTask | AddressBeanForm.jsp |
genstruts | xdoclet.DocletTask | AddressBeanAction.java |
generateHIB | xdoclet.modules.hibernate.HibernateDocletTask | AddressBean.hbm.xml |
generateHIB | xdoclet.modules.hibernate.HibernateDocletTask | hibernate.cfg.xml |
createDDL | xdoclet.modules.hibernate.HibernateDocletTask | dwschema.sql |
generateDD | xdoclet.modules.web.WebDocletTask | web.xml |
generateDD | xdoclet.modules.web.WebDocletTask | struts-config.xml |
XDoclet 是一个有用的、智能的代码生成器,您可以用它自动进行许多日常的 Java 开发任务。不要被它表面的复杂所吓退。随着逐渐精通 XDoclet(以及与之相关的 Apache Ant),您会节约您宝贵的时间,并在未来的开发工作中,得到数倍的回报。