本文摘自
http://www.w3schools.com,虽是英文版,但其脚本用法相信大家还是看得懂的,希望对大家编写基于xml的网页有所帮助。
To read and update - create and manipulate - an XML document, you will need an XML parser.
Microsoft's XML Parser
Microsoft's XML parser is a COM component that comes with Internet Explorer 5 and higher. Once you have installed Internet Explorer, the parser is available to scripts.
Microsoft's XML parser supports all the necessary functions to traverse the node tree, access the nodes and their attribute values, insert and delete nodes, and convert the node tree back to XML.
The following table lists the most commonly used node types supported by Microsoft's XML parser:
Node Type | Example |
---|
Processing instruction | <?xml version="1.0"?> |
Element | <drink type="beer">Carlsberg</drink> |
Attribute | type="beer" |
Text | Carlsberg |
MSXML Parser 2.5 is the XML parser that is shipped with Windows 2000 and IE 5.5.
MSXML Parser 3.0 is the XML parser that is shipped with IE 6.0 and Windows XP.
The MSXML 3.0 parser features:
- JavaScript, VBScript, Perl, VB, Java, C++, etc. support
- Complete XML support
- Full DOM and Namespace support
- DTD and validation
- Complete XSLT and XPath support
- SAX2 support
- Server-safe HTTP
To create an instance of Microsoft's XML parser with JavaScript, use the following code:
var xmlDoc=new ActiveXObject("Microsoft.XMLDOM") |
To create an instance of Microsoft's XML parser with VBScript, use the following code:
set xmlDoc=CreateObject("Microsoft.XMLDOM") |
To create an instance of Microsoft's XML parser in an ASP page (using VBScript), use the following code:
set xmlDoc=Server.CreateObject("Microsoft.XMLDOM") |
The following code loads an existing XML document ("note.xml") into Microsoft's XML parser:
<script type="text/javascript">
var xmlDoc=new ActiveXObject("Microsoft.XMLDOM")
xmlDoc.async="false"
xmlDoc.load("note.xml")
...
...
...
</script> |
The first line of the script above creates an instance of the Microsoft XML parser. The third line tells the parser to load an XML document called "note.xml". The second line turns off asynchronized loading, to make sure that the parser will not continue execution of the script before the document is fully loaded.
XML Parser in Mozilla Browsers
Plain XML documents are displayed in a tree-like structure in Mozilla (just like IE).
Mozilla also supports parsing of XML data using JavaScript. The parsed data can be displayed as HTML.
To create an instance of the XML parser with JavaScript in Mozilla browsers, use the following code:
var xmlDoc=document.implementation.createDocument("ns","root",null) |
The first parameter, ns, defines the namespace used for the XML document. The second parameter, root, is the XML root element in the XML file. The third parameter, null, is always null because it is not implemented yet.
The following code loads an existing XML document ("note.xml") into Mozillas' XML parser:
<script type="text/javascript">
var xmlDoc=document.implementation.createDocument("","",null);
xmlDoc.load("note.xml");
...
...
...
</script> |
The first line of the script above creates an instance of the XML parser. The second line tells the parser to load an XML document called "note.xml".
Loading an XML File - A Cross browser Example
The following example is a cross browser example that loads an existing XML document ("note.xml") into the XML parser:
<html>
<head>
<script type="text/javascript">
var xmlDoc
function loadXML()
{
//load xml file
// code for IE
if (window.ActiveXObject)
{
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
xmlDoc.load("note.xml");
getmessage()
}
// code for Mozilla, etc.
else if (document.implementation &&
document.implementation.createDocument)
{
xmlDoc= document.implementation.createDocument("","",null);
xmlDoc.load("note.xml");
xmlDoc.onload=getmessage
}
else
{
alert('Your browser cannot handle this script');
}
} function getmessage()
{
document.getElementById("to").innerHTML=
xmlDoc.getElementsByTagName("to")[0].firstChild.nodeValue
document.getElementById("from").innerHTML=
xmlDoc.getElementsByTagName("from")[0].firstChild.nodeValue
document.getElementById("message").innerHTML=
xmlDoc.getElementsByTagName("body")[0].firstChild.nodeValue
}
</script>
</head> <body onload="loadXML()" bgcolor="yellow">
<h1>W3Schools Internal Note</h1>
<p><b>To:</b> <span id="to"></span><br />
<b>From:</b> <span id="from"></span>
<hr />
<b>Message:</b> <span id="message"></span>
</p>
</body>
</html> |
Try it yourself
Loading XML Text Into the Parser
Internet Explorer supports two ways of loading XML into a document object: the load() method and the loadXML() method. The load() method loads an XML file and the loadXML() method loads a text string that contains XML code.
The following code loads a text string into Microsoft's XML parser:
<script type="text/javascript"> var txt="<note>"
txt=txt+"<to>Tove</to><from>Jani</from>"
txt=txt+"<heading>Reminder</heading>"
txt=txt+"<body>Don't forget me this weekend!</body>"
txt=txt+"</note>" var xmlDoc=new ActiveXObject("Microsoft.XMLDOM")
xmlDoc.async="false"
xmlDoc.loadXML(txt)
...
...
... </script> |
If you have Internet Explorer, you can try it yourself.
posted @
2006-11-16 12:20 xzc 阅读(2227) |
评论 (1) |
编辑 收藏
模糊测试(Fuzz testing )是一项对代码质量有着深远影响的简单技术。在本文中,Elliotte Rusty Harold 故意将随机的坏数据插入应用程序,以观察发生的结果。他也解释了如何使用如校验和、XML 数据存储及代码验证等防护性编码技术,来加固您的程序以抵制随机数据。他以一个练习进行总结,在练习中他以一个代码破坏者的角度进行思考 —— 这是一种用于防护代码的至关重要的技术。
多年来,我惊叹于有如此大量能够使 Microsoft Word 崩溃的坏文件。少数字节错位,会使整个应用程序毁于一旦。在旧式的、无内存保护的操作系统中,整个计算机通常就这样宕掉了。Word 为什么不能意识到它接收到了坏的数据,并发出一条错误信息呢?为什么它会仅仅因为少数字节被损坏就破坏自己的栈、堆呢?当然,Word 并不是惟一一个面对畸形文件时表现得如此糟糕的程序。
本文介绍了一种试图避免这种灾难的技术。在模糊测试中,用随机坏数据(也称做 fuzz)攻击一个程序,然后等着观察哪里遭到了破坏。模糊测试的技巧在于,它是不符合逻辑的:自动模糊测试不去猜测哪个数据会导致破坏(就像人工测试员那样),而是将尽可能多的杂乱数据投入程序中。由这个测试验证过的失败模式通常对程序员来说是个彻底的震憾,因为任何按逻辑思考的人都不会想到这种失败。
模糊测试是一项简单的技术,但它却能揭示出程序中的重要 bug。它能够验证出现实世界中的错误模式并在您的软件发货前对潜在的应当被堵塞的攻击渠道进行提示。
模糊测试如何运行
模糊测试的实现是一个非常简单的过程:
- 准备一份插入程序中的正确的文件。
- 用随机数据替换该文件的某些部分。
- 用程序打开文件。
- 观察破坏了什么。
可以用任意多种方式改变该随机数据。例如,可以将整个文件打乱,而不是仅替换其中的一部分,也可以将该文件限制为 ASCII 文本或非零字节。不管用什么方式进行分割,关键是将大量随机数据放入应用程序并观察出故障的是什么。
| 测试基于 C 的应用程序 当字符串包含额外的零时,许多用 C 编写的程序都会出问题 —— 这类问题太过频繁以至于额外的零能够彻底隐藏代码中其他的问题。一旦验证出程序存在零字节问题,就可以移除它们,从而让其他的问题浮现出来。 |
|
可以手动进行初始化测试,但要想达到最佳的效果则确实需要采用自动化模糊测试。在这种情况下,当面临破坏输入时首先需要为应用程序定义适当的错误行为。(如果当输入数据被破坏时,您发现程序正常运行,且未定义发生的事件,那么这就是第一个 bug。)随后将随机数据传递到程序中直到找到了一个文件,该文件不会触发适当的错误对话框、消息、异常,等等。存储并记录该文件,这样就能在稍后重现该问题。如此重复。
尽管模糊测试通常需要一些手动编码,但还有一些工具能提供帮助。例如,清单 1 显示了一个简单的 Java™ 类,该类随机更改文件的特定长度。我常愿意在开始的几个字节后面启动模糊测试,因为程序似乎更可能注意到早期的错误而不是后面的错误。(您的目的是想找到程序未检测到的错误,而不是寻找已经检测到的。)
清单 1. 用随机数据替换文件部分的类
import java.io.*;
import java.security.SecureRandom;
import java.util.Random;
public class Fuzzer {
private Random random = new SecureRandom();
private int count = 1;
public File fuzz(File in, int start, int length) throws IOException
{
byte[] data = new byte[(int) in.length()];
DataInputStream din = new DataInputStream(new FileInputStream(in));
din.readFully(data);
fuzz(data, start, length);
String name = "fuzz_" + count + "_" + in.getName();
File fout = new File(name);
FileOutputStream out = new FileOutputStream(fout);
out.write(data);
out.close();
din.close();
count++;
return fout;
}
// Modifies byte array in place
public void fuzz(byte[] in, int start, int length) {
byte[] fuzz = new byte[length];
random.nextBytes(fuzz);
System.arraycopy(fuzz, 0, in, start, fuzz.length);
}
} |
| 关于代码 我可以用很多种方式优化 清单 1 中的代码。例如,有着 java.nio 的内存映射文件是一个相当不错的选择。我也能够改进这个错误处理及可配置性。因为不想让这些细节混淆这里所要说明的观点,所以我将代码保持了原样。 |
|
模糊测试文件很简单。将其传至应用程序通常不那么困难。如 AppleScript 或 Perl 脚本语言通常是编写模糊测试的最佳选择。对于 GUI 程序,最困难的部分是辨认出应用程序是否检测出正确的故障模式。有时,最简单的方法是让一个人坐在程序前将每一个测试通过或失败的结果都标记下来。一定要将所有生成的随机测试用例单独地命名并保存下来,这样就能够重现这个过程中检测到的任何故障。
防护性编码
可靠的编码遵循了这样的基本原则:绝不会让程序中插入未经过一致性及合理性验证的外部数据。
如果从文件中读入一个数字并期望其为正数,那么,在使用其进行进一步处理前对其先验证一下。如果期望字符串只包含 ASCII 字母,请确定它确实是这样。如果认为文件包含一个四字节的整数倍的数据,请验证一下。一定不要假设任何外部提供的数据中的字符都会如您所料。
最常见的错误是做出这样的假设:因为程序将该数据写出,该程序就能不用验证再一次将该数据读回去。这是很危险的!因为该数据很可能已经被另一个程序在磁盘上复写过了。它也可能已经被一个故障磁盘或坏的网络传输所破坏了或已经被另一个带 bug 的程序更改过了。它甚至可能已经被故意更改过以破坏程序的安全性。所以不要假设任何事,要进行验证。
当然,错误处理及验证十分令人生厌,也很不方便,并被全世界程序员们所轻视。计算机的诞生已进入了六十个年头,我们仍旧没有检查基本的东西,如成功打开一个文件及内存分配是否成功。让程序员们在阅读一个文件时测试每一个字节和每一个不变量似乎是无望的 —— 但不这样做就会使程序易被模糊攻击。幸运的是,可以寻求帮助。恰当使用现代工具和技术能够显著减轻加固应用程序的痛苦,特别是如下三种技术更为突出:
- 校验和
- 基于语法的格式,如 XML
- 验证过的代码如 Java
用校验和进行的模糊试验
能够保护程序抵御模糊攻击的最简单的方法是将一个检验和添加到数据中。例如,可以将文件中所有的字节都累加起来,然后取其除以 256 的余数。将得到的值存储到文件尾部的一个额外字节中。然后,在输入数据前,先验证检验和是否匹配。这项简单模式将未被发现的意外故障的风险降低到约 1/256 。
健壮的校验和算法如 MD5 和 SHA 并不仅仅取其除以 256 的余数,它完成的要多得多。在 Java 语言中,java.security.DigestInputStream 和 java.security.DigestOutputStream 类为将一个校验和附属到数据中提供了便捷的方式。使用这些校验和算法中的一种可以将程序遭受意外破坏的机率降低到少于十亿分之一(尽管故意攻击仍有可能)。
XML 存储及验证
将数据以 XML 形式存储是一种避免数据损坏的好方法。XML 最初即着力于 Web 页面、书籍、诗歌、文章及相似文档,它几乎在每个领域都获取了巨大的成功,从金融数据到矢量图形到序列化对象等等。
| 不切实际的限定 如果真想要破坏一个 XML 解析器,有几种方法可以试试。例如,大多数 XML 解析器服从于特定的最大尺寸。如果一个元素名长度超过 22 亿字符(Java String 的最大尺寸),SAX 解析器将会失败。尽管如此,在实践中这些极限值如此之高,以至于在达到之前内存就已经耗尽。 |
|
使 XML 格式抵制模糊攻击的关键特征是一个对输入不做任何 假设的解析器。这就是真正想在一个健壮的文件格式中所获得的。设计 XML 解析器是为了让任何输入(格式良好的或无格式的,有效的或无效的)都以定义好的形式处理。XML 解析器能够处理任何 字节流。如果数据首先通过了 XML 解析器,则仅需要准备好接受解析器所能提供的东西。例如,不需要检查数据是否包含空字符,因为 XML 解析器绝不会传送一个空值。如果 XML 解析器在其输入中看到一个空字符,它就会发出异常并停止处理。当然还需要处理这个异常,但编写一个 catch 块来处理检测到的错误比起编写代码来检测所有可能的错误来说要简单得多。
为使程序更加安全,可以用 DTD 和/或模式来验证文档。这不仅检查了 XML 是否格式良好,而且至少与所预期更加接近。验证并不会告知关于文档所需了解的一切,但它却使编写大量简单检查变得很简单。用 XML,很明显能够将所接受的文档严格地限定为能够处理的格式。
尽管如此,还有多段代码不能用 DTD 或模式进行验证。例如,不能测试发票上商品的价格是否和数据库中库存商品的价格一致。当从客户接收到一份包含价格的订单文档时,不论其是 XML 格式或是其他格式,在提交前通常都会检查一下,以确保客户并未修改价格。可以用定制代码实现这些最后的检查。
基于语法的格式
使 XML 能够对模糊攻击具有如此的抵御能力的是其使用巴科斯-诺尔范式(Backus-Naur Form,BNF)语法仔细且标准地定义的格式。许多解析器都是使用如 JavaCC 或 Bison 等解析器-生成器工具直接从此语法中构建的。这种工具的实质是阅读一个任意的输入流并确定其是否符合此语法。
如果 XML 并不适合于您的文件格式,您仍可以从基于解析器的解决方案的健壮性中获益。您必须为文件格式自行编写语法,随后开发自己的解析器来阅读它。相比使用唾手可得的 XML 解析器,开发自己的解析器需要更多的工作。然而它是一个更为健壮的解决方案,而不是不根据语法正式地进行验证就将数据简单地装载到内存中。
Java 代码验证
由模糊测试导致的许多故障都是内存分配错误及缓冲器溢出的结果。用一种安全的垃圾收集语言(在如 Java 或 managed C# 等虚拟机上执行的)来编写应用程序避免了许多潜在问题。即使用 C 或 C++ 来编写代码,还是需要使用一个可靠的垃圾收集库。在 2006 年,台式机程序员或服务器程序员不应该还需要管理内存。
Java 运行时对其自身的代码起到了额外保护层的作用。在将一个 .class 文件装载到虚拟机之前,该文件要由一个字节符验证器或一个可选的 SecurityManager 进行验证。Java 并不假设创建 .class 文件的编译器没有 bug 且运转正常。设计 Java 语言之初就是为了允许在一个安全沙箱中运行不信任的、潜在恶意的代码。它甚至不信任其自身编译过的代码。毕竟,也许有人已经用十六进制编辑器手工修改了字节符,试图触发缓冲器溢出。我们大家都应该对我们的程序也有对输入这样的偏执。
以敌人的角度思考
之前介绍的每项技术都在阻止意外破坏方面造诣颇深。将它们综合起来恰当地实现,会将未被发现的非蓄意破坏发生的可能性几乎减少到零。(当然,并不会减少到零,但其发生的可能性就如同一束偏离轨道的宇宙射线将您 CPU 运算 1+1 的结果变为 3 的可能性一样微乎其微。)但不是所有的数据损坏都是非蓄意的。如果有人故意引入坏数据来破坏程序的安全性又该如何呢?以一个攻击者的角度进行思考是防护代码的下一个步骤。
转回到一个攻击者的角度进行思考,假设要攻击的应用程序是用 Java 编程语言编写的、使用非本地代码且将所有额外数据都以 XML(在接受前经过彻底验证)形式存储,还能成功攻击吗?是的,能。但用随机改变文件字节的低级方法显然不行。需要一种更为复杂的方法来说明程序自身的错误检测机制及路径。
当测试一个抵御模糊攻击的应用程序时,不可能做纯黑盒测试,但通过一些明显的修改,基本的想法还是可以应用的。例如,考虑校验和,如果文件格式包含一个校验和,在将文件传至应用程序前仅仅修改此校验和就可以使其同随机数据相匹配。
对于 XML,试着模糊单独的元素内容和属性值,而不是从文档中挑选一部分随机的字节进行替换。一定要用合法的 XML 字符替换数据,而不要用随机字节,因为即使一百字节的随机数据也一定是畸形的。也可以改变元素名称和属性名称,只要细心地确保得到的文档格式仍是正确的就可以了。如果该 XML 文档是由一个限制非常严格的模式进行检查的,还需要计算出该模式没有 检查什么,以决定在哪里进行有效的模糊。
一个结合了对剩余数据进行代码级验证的真正严格的模式也许不会留下可操纵的空间。这就是作为一个开发人员所需要追求的。应用程序应能够处理所发送的任何有意义的字节流,而不会因权利上( de jure ) 无效而拒绝。
结束语
模糊测试能够说明 bug 在程序中的出现。并不证明不存在这样的 bug。而且,通过模糊测试会极大地提高您对应用程序的健壮性及抵御意外输入的安全性的自信心。如果您用 24 小时对程序进行模糊测试而其依然无事,那么随后同种类型的攻击就不大可能再危及到它。(并不是不可能,提醒您,只是可能性很小。)如果模糊测试揭示出程序中的 bug,就应该进行修正,而不是当 bug 随机出现时再对付它们。模糊测试通过明智地使用校验和、XML、垃圾收集和/或基于语法的文件格式,更有效地从根本上加固了文件格式。
模糊测试是一项用于验证程序中真实错误的重要工具,也是所有意识到安全性问题且着力于程序健壮性的程序员们的工具箱中所必备的工具。
关于作者
|
| | Elliotte Rusty Harold 来自新奥尔良, 现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他和妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,同住的还有他的猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学计算机科学的副教授,他在该校讲授 Java 和面向对象编程。他的 Web 站点 Cafe au Lait 已经成为 Internet 上最流行的独立 Java 站点之一,它的姊妹站点 Cafe con Leche 已经成为最流行的 XML 站点之一。他的书包括 Effective XML、 Processing XML with Java、 Java Network Programming 和 The XML 1.1 Bible。他目前在从事处理 XML 的 XOM API、Jaxen XPath 引擎和 Jester 测试覆盖率工具的开发工作。 |
posted @
2006-11-08 12:49 xzc 阅读(359) |
评论 (0) |
编辑 收藏
● xdoclet学习续
Xdoclet是一个代码自动生成的工具
XDoclet任务就是Ant的自定义任务,除此以外,没有其他运行XDoclet任务的方法。
XDoclet它有两个重要的组件:
进行特殊标记的 Java 源文件。
预先定义的模板。[引用]
Merge File用来处理无法在Source Code中加xdoclet tag的情况。
Ø XDoclet中的核心任务:
<ejbdoclet>:面向EJB领域,生成EJB、工具类和布署描述符。
<webdoclet>:面向Web开发,生成serlvet、自定义标签库和web框架文件。
<hibernatedoclet>:Hibernate持续,配置文件、Mbeans
<jdodoclet>:JDO,元数据,vender configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用户自定义模板来生成代码。
<documentdoclet>:生成项目文件(例如todo列报表)
Ø webdoclet sub task
XDoclet并没有和Ant一起发布,所以如果你想要使用XDoclet的话,就需要单独的下载和
安装。在使用任何一个XDoclet的任务之前,你首先需要在使用Ant的<taskdef>任务来声
明它。
<deploymentdescriptor>:产生标准的web引用配置文件web.xml,destdir属性设定
web.xml文件的存放位置。
XDoclet通过合并点(merge points)支持定制,合并点是在模板文件定义里允许运行时插入定制代码的地方,使用mergedir属性设置。
<target name="webdoclet" depends="compile-web"
unless="webdoclet.unnecessary"
description="Generate web and Struts descriptors">
<taskdef name="webdoclet" classname="xdoclet.modules.web.WebDocletTask">
<classpath>
<path refid="xdoclet.classpath"/>
<path refid="web.compile.classpath"/>
</classpath>
</taskdef>
<!—mergedir完成模板文件合并的功能-->
<webdoclet destdir="${webapp.target}/WEB-INF"
force="${xdoclet.force}"
mergedir="metadata/web"
excludedtags="@version,@author"
verbose="true">
<!—找出目录src/web 和${build.dir}/web/gen文件中的webdoclet注
释,生成web.xml,struts-config.xml等配置信息文件-->
<fileset dir="src/web"/>
<fileset dir="${build.dir}/web/gen"/>
<!—生成的web.xml文件放在build/appName/WEB-INF/-->
<deploymentdescriptor validateXML="true"
servletspec="2.3" sessiontimeout="10"
destdir="${webapp.target}/WEB-INF" distributable="false"
displayname="${ant.project.name}">
<configParam name="httpPort" value="${http.port}"/>
<configParam name="httpsPort" value="${https.port}"/>
<configParam name="daoType" value="${dao.type}"/>
<configParam name="security" value="${security.mode}"/>
</deploymentdescriptor>
<jsptaglib validateXML="true"
description="Custom tag library for this application"
shortName="${webapp.name}" filename="${webapp.name}.tld"/>
<strutsconfigxml validateXML="true" version="1.2"/>
<strutsvalidationxml/>
</webdoclet>
</target>
图表 1引用一张网友发表的非常直观的使用说明图
Ø XDoclet 中的合并点
在 XDoclet 的文档中,您会非常频繁地看到术语 合并点(merge point)和 合并文件(merge file)。合并文件是文本文件,您可以把它合并到 XDoclet 生成代码的指定位置——“合并点”上(由模板指定)。可以用合并文件来包含静态文本(例如代码片断和 XML 片断),这些文本可能很难或者不能用 XDoclet 的能力生成。例如,在示例代码的 metadata/web 目录下,您会找到这些文件。在代码生成期间,可以用到这些文件合并配置文件的一部分[引用]。
ØXDoclet中的模板
XDoclet使用代码模板来生成代码。模板(template)是你想生成文件的原型。模板里使
用一些XML标签来指导模板引擎如何根据输入类以及它们的元数据来调整代码的生成。
模板有点像JSP文件。它们都包含文件和XML标签,生成输出文件时XML标签会被解析,
然后生成文本并显示在XML标签所处的位置上。除了以XDt为命名空间打头的XML标签
会被XDoclet引擎解析以外,其余的XML标签XDoclet会忽略不管。
Ø AppFuse中生成Action类的XDoclet模板
public final class <XDtClass:className/>Action extends BaseAction {
public ActionForward cancel(ActionMapping mapping, ActionForm form,HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return search(mapping, form, request, response);
}
public ActionForward delete(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (log.isDebugEnabled()) {
log.debug("Entering 'delete' method");
}
ActionMessages messages = new ActionMessages();
<XDtClass:className/>Form <XDtForm:classNameLower/>Form = (<XDtClass:className/>Form) form;
// Exceptions are caught by ActionExceptionHandler
Manager mgr = (Manager) getBean("manager");
mgr.removeObject(<XDtClass:className/>.class, new Long(<XDtForm:classNameLower/>Form.getId()));
messages.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("<XDtForm:classNameLower/>.deleted"));
// save messages in session, so they'll survive the redirect
saveMessages(request.getSession(), messages);
return search(mapping, form, request, response);
}
… …
}
Ø AppFuse中自动生成的对应Action类
public final class PersonAction extends BaseAction {
public ActionForward cancel(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return search(mapping, form, request, response);
}
public ActionForward delete(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (log.isDebugEnabled()) {
log.debug("Entering 'delete' method");
}
ActionMessages messages = new ActionMessages();
PersonForm personForm = (PersonForm) form;
// Exceptions are caught by ActionExceptionHandler
Manager mgr = (Manager) getBean("manager");
mgr.removeObject(Person.class, new Long(personForm.getId()));
messages.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("person.deleted"));
// save messages in session, so they'll survive the redirect
saveMessages(request.getSession(), messages);
return search(mapping, form, request, response);
}
… …
}
posted @
2006-11-05 09:00 xzc 阅读(441) |
评论 (0) |
编辑 收藏
XDoclet起步
XDoclet是一个代码生成工具,它可以把你从Java开发过程中繁重的重复劳动中解脱出来。XDoclet可以让你的应用系统开发的更加快速,而你只要付比原先更少的努力。你可以把你手头上的冗长而又必需的代码交给它帮你完成,你可以逃脱“deployment descriptor地狱”,你还可以使你的应用系统更加易于管理。而你所要做的,只不过是在你的注释里,多加一些类javadoc属性。然后,你会惊讶于XDoclet为了做到的一切。
讨论XDoclet,有一点比较容易产生混淆,那就是XDoclet不但是一系统的代码生成应用程序,而且它本身还是一个代码生成框架。虽然每个应用系统的细节千变万化(比如EJB代码生成和Struts代码生成是不一样的,而JMX代码生成又是另一番景象),但这些代码生成的核心概念和用法却是类似的。
在这一章里,我们将会看到渗透到所有XDoclet代码生成程序当中的XDoclet框架基础概念。但在之前,我们先从一个例子入手。
2.1 XDoclet in action
每一个程序员都会认识到,他们的程序永远也不会完成。总会有另一些的功能需要添加,另一些的BUG需要修正,或者需要不断的进行重构。所以,在代码里添加注释,提醒自己(或者其他的程序员)有哪些任务需要完成已成为一个共识。
如何来跟踪这些任务是否完成了呢?理想情况下,你会收集整理出来一个TODO任务列表。在这方面,XDoclet提供了一个强大的TODO生成器,来帮助你完成这个任务。这是一个把XDoclet引入项目的好机会。
2.1.1 一个公共的任务
假设你正在开发一个使用了勺子的类。
public class Matrix {
// TODO – 需要处理当没有勺子的情况
public void reload() {
// ...
Spoon spoon = getSpoon();
// ...
}
}
理想情况下,你在下一次阅读这段代码的时候,你会处理这个“空勺子”(null spoon)的问题。但如果你过了很久才回来看这段代码,你还会记得在这个类里还有一些工作要做吗?当然,你可以在你的源码里全局搜索TODO,甚至你的集成开发环境有一个内建的TODO列表支持。但如果你想把任务所在的类和方法也标注出来的话,XDoclet可以是另一种选择。XDoclet可以为你的项目生成一个TODO报表。
2.1.2 添加XDoclet标签
为了把你的TODO项目转换成另一种更加正式的格式,你需要对代码进行一些细微的改动。如下所示:
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核心代码生成应用程序中的一个。
现在,你可以在Ant构建文件中加入一个todo目标调用这个任务来生成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 创建一个更加职业化的TODO报表
XDoclet生成的TODO报表可以有一个更加职业化的外表。报表会列出一个概览,显示在哪个包哪个类里有todo项(以及todo项的个数)。Todo项可以跟在方法、类和域上,从报表上可以清楚的区别它们。类级别的todo项会标注class,方法级别的todo项会在方法签名上标注M。构造函数和域相关的todo项也会进行相似的标注。
这个任务看起来很简单,但考虑到你所需要做的只是在注释上添加一些格式化的@todo标签,相对于那种只有人才可以理解的无格式的松散的注释,这种标签是机器可读的,也更容易编程处理。生成的输出也更容易阅读并且更加的商业化。
2.2 任务和子任务
生成todo报表,只是XDoclet可以完成的事情当中的冰山一角。当初,XDoclet因为可以自动生成EJB繁杂的接口和布署描述文件而声名鹊起。然而,现在的XDoclet已经发展成了一个全功能的、面向属性的代码生成框架。J2EE代码生成只是XDoclet的一个应用方面,它可以完成的任务已经远远超越了J2EE和项目文档的生成。
2.2.1 XDoclet 任务
到现在为止,我们一直在讨论使用XDoclet生成代码,但事实上,更确切的说法应该是,我们使用XDoclet的一个特定的任务来生成代码,比如<ejbdoclet>。每一个XDoclet任务关注于一个特定的领域,并提供这个领域的丰富的代码生成工具。
[定义:任务(Tasks)是XDoclet里可用的代码生成应用程序的高层概念。]
在XDoclet里,目前已有如下所示的七个核心任务。
<ejbdoclet>:面向EJB领域,生成EJB、工具类和布署描述符。
<webdoclet>:面向Web开发,生成serlvet、自定义标签库和web框架文件。
<hibernatedoclet>:Hibernate持续,配置文件、Mbeans
<jdodoclet>:JDO,元数据,vender configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用户自定义模板来生成代码。
<documentdoclet>:生成项目文件(例如todo列报表)
这其中,<ejbdoclet>最常用,并且很多项目也仅仅使用XDoclet来进行EJB代码生成。<webdoclet>是其次一个常用的代码生成任务。当然,在一个项目中同时使用几个XDoclet任务是可能的(并且也是推荐的),但在这些任务之间是完全独立的,它们彼此之间并不能进行直接的交流。
2.2.2 XDoclet子任务
XDoclet的任务是领域相关的,而在某个特定领域的XDoclet任务,又由许许多多紧密耦合在一起的子任务组成的,这些子任务每个都仅仅执行一个非常特定和简单的代码生成任务。
[定义:子任务(subtasks)是由任务提供的单目标的代码生成过程]
任务提供子任务执行时的上下文,并且把这些相关的子任务组织管理了起来。任务会依赖这些子任务来生成代码。在一个任务当中调用多个子任务来协同完成各种各样比较大型的代码生成任务是非常常见的。比如,在开发EJB时,你可能想要为每一个bean生成一个home接口,一个remote接口以及ejb-jar.xml布署描述符文件。这就是在<ejbdoclet>任务的上下文环境中的三个独立的代码生成子任务。
子任务可以随意的组合排列,以满足项目代码生成的需要。某个XDoclet任务包含的子任务经常会共享功能和在源文件中使用相同的XDoclet标签。这意味着当你开始一个任务的时候,你可以很容易的集成进一个相关的子任务,而不需要很大的改动。
子任务交互
让我们以<ejbdoclet>任务为例,看一下相关的子任务之间是如何进行关联的。假设你正在开发一个CMP(容器管理持久化)实体Bean。你想要使用一些<ejbdoclet>的子任务:
•<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
•<localhomeinterface>:生成local home接口。
•<localinterface>:生成local接口。
在执行如上子任务的时候,你需要标记出你的实体Bean的CMP域。当你发布你的bean的时候,你还需要在开发商相关的布署描述符中提供某个特定的关系数据库中的特定表和列与你的CMP实体Bean的映射关系。XDoclet可以让你在原先已存在的CMP XDoclet属性基础上再加上一些关系映射属性,然后,你就可以在任务中加入一个开发商相关的子任务(例如<jboss>或者<weblogic>)来生成布署描述符文件。XDoclet提供了几乎所有的应用服务器的支持,你只需要一些初始化的小改动,就可以进行这些应用服务器相关的代码生成了。
但那只是冰山一角。你还可以使用<entitycmp>子任务为为你的bean生成一个实体bean接口的实现子类。如果你使用<valueobject>子任务来为了你的bean生成值对象,<entityemp>子任务还会为你的值对象生成方法的实现代码。
觉得不可思议了吧。可惜XDoclet没有提供<cupofcoffee>子任务,要不然我们可以喝杯咖啡,休息一下啦。
这里不是想向你介绍<ejbdoclet>所有的子任务或者<ejbdoclet>可以完成的所有代码生成功能,而仅仅是想向你展示一下任务的子任务之间是如何工作在一起的。一旦你开始并熟悉了一个XDoclet 子任务,熟悉另一个子任务会变得非常简单- 那种每个子任务都是孤立的相比,使用这种可以相互协作的子任务,开发成本会显著的降低,效果也更加的立竿见影。
2.3 使用Ant执行任务
XDoclet“嫁”给了Ant。XDoclet任务就是Ant的自定义任务,除此以外,没有其他运行XDoclet任务的方法。所幸的是,Ant已经成为了Java构建工具事实上的标准,所以这不算什么限制。事实上,反过来,XDoclet与Ant的这种“亲密”关系使得XDoclet可以参与到任何Ant构建过程当中去。
2.3.1 声明任务
XDoclet并没有和Ant一起发布,所以如果你想要使用XDoclet的话,就需要单独的下载和安装。在使用任何一个XDoclet的任务之前,你首先需要在使用Ant的<taskdef>任务来声明它。例如:
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的话,你就会知道这段代码是告诉Ant加载<ejbdoclet>的任务定义。当然,你也可以以你喜欢的任何方式来命名这个自定义任务,但最好还是遵守标准的命名规律以免发生混淆。classname和classpathref属性告诉Ant到哪里去找实现这个自定义任务的XDoclet类。如果你想使用其他的XDoclet任务,就必须要类似这样首先声明这个任务。
一般共通的做法是,把所有需要使用的XDoclet任务都放在Ant的一个目标里声明,这样在其他的目标里如果需要使用这些任务,只要depends这个任务就可以了。你可能已经在Ant的构建文件里包含了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>
现在,任务声明好了,XDoclet“整装待发”。
2.3.2 使用任务
你可以在任何目标里使用声明好的任务。在任务的上下文环境里,可以调动相关的子任务。让我们看一个例子,这个例子调用了<ejbdoclet>任务。不要担心看不懂语法的细节,现在你只需要关心一些基础概念就可以了。
<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>
把任务想像成一个子程序运行时需要的一个配置环境(记住,子任务才是真正进行代码生成工作的)。当调用一个子任务时,子任务从任务继承上下文环境,当然,你也可以根据需要随意的覆盖这些值。在上面的例子里,因为<deploymentdescriptor>子任务生成的布署描述符文件和其他生成各种接口的子任务生成的Java源文件需要放在不同的位置,所以覆盖了destdir的属性值。布署描述符文件需要放在一个在打包EJB JAR文件的时候可以容易包含进来的地方,而生成的Java代码则需要放置在一个可以调用Java编译器进行编译的地方。需要这些子任务之间是紧密关联的,但只要你需要,你可以有足够的自主权控制任务的生成环境。
<fileset>属性同样被应用到所有的子任务。这是一个Ant的复杂类型(相对于文本和数值的简单类型),所以以子元素的方式在任务中声明。不要把它和子任务混为一谈。当然,如果你想在某个子任务中另外指定一个不同的输入文件集,你也可以在这个子任务中放置一个<fileset>子元素来覆盖它。
子任务的可配置选项远远不止这些。我们会在下一章继续介绍所有的任务和子任务,以及常用的配置选项。
2.4 用属性标注你的代码
可重用的代码生成系统需要输入来生成感兴趣的输出。一个解析器生成器也需要一个语言描述来解析生成解析器。一个商务对象代码生成器需要领域模型来知道要生成哪些商务对象。XDoclet则需要Java源文件做为输出来生成相关的类或者布署/配置文件。
然而,源文件可能并没有提供代码生成所需要的所有信息。考虑一个基于servlet的应用,当你想生成web.xml文件的时候,servlet源文件仅可以提供类名和适当的servlet接口方法。其他的信息比如URI pattern映射、servlet需要的初始化参数等信息并没有涵盖。显而易见,如果class并没有提供这些信息给你,你就需要自己手动在web.xml文件时填写这些信息。
XDoclet当然也不会知道这些信息。幸运的是,解决方法很简单。如果所需信息在源文件时没有提供,那就提供它,做法就是在源文件里加入一些XDoclet属性。XDoclet解析源文件,提取这些属性,并把它们传递给模板,模板使用这些信息生成代码。
2.4.1 剖析属性
XDoclet属性其实就是javadoc的扩展。它们在外表上和使用上都有javadoc属性一样,可以放置在javadoc文档注释里。文档注释以/**开始,*/结尾。下面是一个简单的例子:
/**
* 这是一段javadoc注释。
* 注释可以被分解成多行,每一行都以星号开始。
*/
在注释里的所有文本都被视为javadoc注释,并且都能够被XDoclet访问到。注释块一般都与Java源文件中的某个实体有关,并紧跟在这个实体的前面。没有紧跟实体的注释块将不会被处理。类(或者接口)可以有注释块,方法和域也可以有自己的注释块,比如:
/**
* 类注释块
*/
public class SomeClass {
/** 域注释块 */
private int id;
/**
* 构造函数注释块
*/
public SomeClass() {
// ...
}
/**
* 方法注释块
*/
public int getId() {
return id;
}
}
注释块分成两部分:描述部分和标签部分。当遇到第一个javadoc标签时,标签部分开始。Javadoc标签也分成两部分:标签名和标签描述。标签描述是可选的,并且可以多行。例如:
/**
* 这是描述部分
* @tag1 标签部分从这里开始
* @tag2
* @tag3 前面一个标签没有标签描述。
* 这个标签有多行标签描述。
*/
XDoclet使用参数化标签扩展了javadoc标签。在XDoclet里,你可以在javadoc标签的标签描述部分加入name=”value”参数。这个微小的改动大大增强了javadoc标签的表达能力,使得javadoc标签可以用来描述复杂的元数据。下面的代码显示了使用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();
参数化标签允许组合逻辑上相关联的属性。你可以加入描述这个类的元信息,使得这个类的信息足够生成代码。另外,程序员借由阅读这样的元信息,可以很快的理解这个类是如何使用的。(如果这个例子上的元信息你看不懂,不要担心,在第4章里,我们会学到EJB相关的标签以及它们的涵意。)
另外,请注意上面的例子中,所有的标签名都以ejb开头。XDoclet使用namespace.tagname的方式给标签提供了一个命名空间。这样做除了可以跟javadoc区别开来以外,还可以把任务相关的标签组织起来,以免任务之间的标签产生混淆。
2.5 代码生成模式
XDoclet是一种基于模板的代码生成引擎。从高层视图上来看,输出文件其实就是由解析执行各式各样的模板生成出来的。如果你理解了模板以及它所执行的上下文环境,就可以确切的认识到,XDoclet可以生成什么,不可以生成什么。如果你正在评估XDoclet平台,理解这些概念是非常重要的。要不然,你可能会错过XDoclet的许多强大的功能,也可能会被XDoclet的一些限制感到迷惑。
XDoclet运行在在Ant构建文件环境中,它提供了Ant自定义任务和子任务来与XDoclet引擎交互。任务是子任务的容器,子任务负责执行代码生成。子任务调用模板。模板提供了你将生成代码的饼干模子。XDoclet解析输入的源文件,提取出源文件中的XDoclet属性元数据,再把这些数据提供给模板,驱动模板执行。除此之外,模板还可以提供合并点(merge points),允许用户插入一些模板片断(合并文件merge files)来根据需要定制代码生成。
2.5.1 模板基础
XDoclet使用代码模板来生成代码。模板(template)是你想生成文件的原型。模板里使用一些XML标签来指导模板引擎如何根据输入类以及它们的元数据来调整代码的生成。
[定义:模板(template)是生成代码或描述文件的抽象模视图。当模板被解析的时候,指定的细节信息会被填入。]
模板一般情况下会有一个执行环境。模板可能应用在一个类环境(转换生成transform generation),也有可能应用在一个全局环境(聚集生成aggregate generation)。转换生成和聚集生成是XDoclet的两种类型的任务模式,理解它们之间的区别对于理解XDoclet是非常重要的。
当你使用XDoclet生成布置描述符文件时,你使用的是聚集生成。布置描述符文件并不仅仅只与一个类相关,相反,它需要从多个类里聚集信息到一个输入文件。在这种生成模式里,解析一次模板只会生成一个输出文件,不管有多少个输入文件。
在转换生成模式里,模板遇到每一个源文件就会解析一次,根据该文件类的上下文环境生成输出。这种生成模式会为每一个输入文件生成一个输出文件。
转换生成模式的一个很好的例子是生成EJB的local和remote接口。显然,接口是和Bean类一一相关的。从每一个类里提取信息(类以及它的方法、域、接口以及XDoclet属性等信息)转换出接口。除此以外,不需要其他的信息。
从实现里提取出接口似乎有点反向。如果你手写程序的话,一般来说会先定义一个接口,然后再写一个类来关现它。但XDoclet做不到,XDoclet不可能帮你实现一个已有接口,因为它不可能帮你生成你的业务逻辑。当然,如果业务逻辑可以从接口本身得到(比如JavaBean的get/set访问器)或者使用XDoclet属性声明好,那么生成业务逻辑代码来实现一个接口也不是不可能。但一般情况下,这样做不太现实。相比而言,提供一个实现,并描述接口与这个实现之间的关联就容易多了。
聚集生成和转换生成主要区别在它们的环境信息上。即使一个代码生成任务中生成一个Java文件,一般也不常用聚集生成,因为生成一个Java类还需要一些重要信息如类所处的包以及你想生成的类名,在这种环境下是无法提供的。如果一定要使用聚集生成的话,那就需要在另一个单独的地方提供好配置信息了。
2.5.2 模板标签
在还没见到模板长啥样子之前,我们已经比较深入的认识它了。那模板文件究竟长啥样子呢?它有点像JSP文件。它们都包含文件和XML标签,生成输出文件时XML标签会被解析,然后生成文本并显示在XML标签所处的位置上。除了以XDt为命名空间打头的XML标签会被XDoclet引擎解析以外,其余的XML标签XDoclet会忽略不管。下面的代码片断显示了XDoclet模板的“经典造型”:
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;
}
}
研究一下这个模板,你会发现,它生成的是一个类定义。这个类里定义了一个静态变量instance,并且使用一个静态方法来控制这个静态文件的访问。借助Java语法,你可以很容易的推断出那些XDoclet模板标签的目录是生成类名,虽然对于这个标签如何工作你还并不是很了解。
即使你从没打算过要自己写模板,但理解模板是如何被解析运行的还是很有必要的。迟早你会调用到一个运行失败的XDoclet任务,没有产生你所期望的输出,那么最快捷的找出原因的方法就是直接检查模板文件,看看是哪里出了问题。
让我们看一下生成静态域定义的片断:
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
在XDoclet的眼里,这段模板代码很简单,那就是:
static <tag/> _instance = null;
XDoclet解析执行标签,如果有输出的话,把输入置回到文本里去。有些标签会执行一些运算,把输出放回到一个流里。这样的标签称之为内容标签(content tags),因为它们产生内容。
另一种类型的标签称之为BODY标签。BODY标签在开始和结束标签之间存在文本。而BODY标签强大就强大在这些文本自己也可以是一断可以由外围标签解析的模板片断。比如在上面的例子里,XDtClass:classOf标签,它们的内容就是模板片断:
<XDtEjbFacade:remoteFacadeClass/>
classOf标签解析这段模板,提取出全限制的内容,然后剃除前面的包面,只输出类名。BODY标签并不总是会解析它的内容,在做这件事之前,它们会事先检查一些外部判断条件(比如检查检查你正在生成的是一个接口还是一个类)。这里标标签称之为条件标签(conditional tags)。还有一些BODY标签提供类似迭代的功能,它的内容会被解析多次。比如一个标签针对类里的每一个方法解析一次内容。
XDoclet标签提供了许多高层次的代码生成功能,但是有时候,它们可能显得不够灵活,或者表达能力满足不了你的需要。这时候,相对于另外开发一套通用功能的模板引擎相比,你可以选择扩展XDoclet模板引擎。你可以使用更具表述能力、功能更加强大的Java平台开发你自己的一套标签。
2.6 使用合并定制
代码生成系统之所以使用的不多,主要原因就在于它们往往只能生成一些死板的、不够灵活的代码。大多数代码生成系统不允许你改动它们生成的代码;如果,如果这个系统不够灵活,你所能做到的最好的扩展就是应用继承扩展生成的代码,或者使用一些共通的设计模式(比如Proxy和Adaptor)来满足你的需要。无论如此,这都不是产生你想生成的代码的好办法。代码生成器最好能做到所生成即所得WYGIWYG(what you generate is what you get),来取代你需要花费大量的时间来粉饰生成出来的并不满足要求的代码。所以,对于代码生成器来说,支持灵活的定制,是生成能够完全满足要求的代码的前提条件。
XDoclet通过合并点(merge points)支持定制——合并点是在模板文件定义里允许运行时插入定制代码的地方。有时候,合并点甚至可以影响到全局代码的生成,不但允许你添加一些定制内容,还可以从根本上改变将要生成出来的东西。
[定义:合并点(Merge points)是模板预先定义的允许你在代码生成的运行时加入定制内容的扩展点]
让我们研究一段从XDoclet源代码里摘取出来的模板代码。在为实体Bean生成主键的模板末尾,定义了这样一个合并点:
<XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目录下创建了一个名为entitypk-custom.xdt文件,那么这个模板文件的内容将会在这个合并点被包含进来。你的定制可以执行高层模板可以执行的所有强大功能,可以进行所有模板可以进行的运算(包括定义自定义标签,定义它们自己的合并点)。
上面的这种合并点,为所有的类环境使用了同一个文件。当然,也可以为每一个类环境使用不同的合并文件。如果你不想定制全部的类文件,或者你不想为了某些改动而重写模板的时候,这会很有用。不管动机是什么,逐类的合并点很容易识别出来:他们会在名字里包含一个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},再根据替换后的文件名去寻找合并文件。例如,如果你有一个名为BlogFacadeBean的Bean,XDoclet会尝试寻找一个名为ejb-src-rolerefs-BlogFacade.xml的合并文件。
如果找不到这个合并文件,则这个<merge>标签的内容模板会被解析。这意味着合并点不仅可以提供定制内容,还可以在一个模板文件里定义一个替换点,当定制内容不存在的时候使用替换点里的内容。不是所有的XDoclet任务都提供了有替换内容的合并点,一般来说,它们更倾向于只提供一个简单的合并点,仅仅当合并文件存在的时候解析并导入合并文件的内容。这取决于任务的开发者觉得哪种合并点更符合他的要求。
还有一点没有介绍到的是XDoclet如何定位合并文件,每一个XDoclet任务或者子任务都会提供一个mergeDir属性,这个属性用于设置你存放合并文件的目录。
posted @
2006-11-05 08:34 xzc 阅读(363) |
评论 (0) |
编辑 收藏
posted @
2006-11-05 08:33 xzc 阅读(260) |
评论 (0) |
编辑 收藏
XDoclet是生成配置文件的强有力的工具,在使用Spring框架时,手动编写Spring配置文件极其繁琐,而且容易遗漏,利用XDoclet即可轻松生成配置文件。
XDoclet支持的Spring方法注入包括:ref,list,name和value。遗憾的是,在某个项目中,我需要注入一个包含Bean引用的List:
public void setHandlers(List handlers) {
...
}
然后,XDoclet并不支持元素为引用类型的List,倘若使用下列注释:
/**
* @spring.property list="articleHandler,imageHandler"
*/
public void setHandlers(List handlers) {
...
}
生成的配置文件如下:
<property name="handlers">
<list>
<value>articleHandler</value>
<value>imageHandler</value>
</list>
</property>
毫无疑问,在Spring启动时,一个ClassCastException将被抛出,因为无法将String类型转化为我们自定义的Handler引用类型。
幸运的是,XDoclet良好的可扩展性使我们能够轻松扩展需要的配置,甚至不需要我们利用XDoclet提供的API编写代码。XDoclet提供一种XML结构的模版语言来生成配置文件,对于Spring配置文件,对应的XML配置文件在xdoclet-spring-module-1.2.3.jar/xdoclet/modules/spring/resources/spring_xml.xdt中。
解开jar包,修改spring_xml.xdt,增加如下XML片断(红色部分):
<XDtMethod:forAllMethods superclasses="true">
<XDtMethod:ifHasMethodTag tagName="spring.property">
<property name="<XDtMethod:propertyName/>">
<XDtMethod:ifHasMethodTag tagName="spring.property" paramName="value">
<value><XDtMethod:methodTagValue tagName="spring.property" paramName="value"/></value>
</XDtMethod:ifHasMethodTag>
<XDtMethod:ifHasMethodTag tagName="spring.property" paramName="ref">
<ref bean="<XDtMethod:methodTagValue tagName="spring.property" paramName="ref"/>"/>
</XDtMethod:ifHasMethodTag>
<XDtMethod:ifHasMethodTag tagName="spring.property" paramName="list">
<list>
<XDtMethod:forAllMethodTagTokens tagName="spring.property" paramName="list">
<value><XDtMethod:currentToken/></value>
</XDtMethod:forAllMethodTagTokens>
</list>
</XDtMethod:ifHasMethodTag>
<XDtMethod:ifHasMethodTag tagName="spring.property" paramName="list.ref">
<list>
<XDtMethod:forAllMethodTagTokens tagName="spring.property" paramName="list.ref">
<ref bean="<XDtMethod:currentToken/>"/>
</XDtMethod:forAllMethodTagTokens>
</list>
</XDtMethod:ifHasMethodTag>
</property>
</XDtMethod:ifHasMethodTag>
</XDtMethod:forAllMethods>
注意红色部分的代码,我们仿照list,增加一个list.ref来实现引用类型的List。
现在,修改注释如下:
/**
* @spring.property list.ref="articleHandler,imageHandler"
*/
public void setHandlers(List handlers) {
...
}
备份好原有的xdoclet-spring-module-1.2.3.jar,然后将修改后的目录打包:
jar cvf xdoclet-spring-module-1.2.3.jar .
替换原来的xdoclet-spring-module-1.2.3.jar,运行XDoclet,顺利生成预期配置:
<property name="handlers">
<list>
<ref bean="articleHandler"/>
<ref bean="imageHandler"/>
</list>
</property>
类似的,我们还可以增加XDoclet对Map注入的支持。
posted @
2006-11-05 08:32 xzc 阅读(597) |
评论 (0) |
编辑 收藏
本文的目的是让你用最短的时间了解xdoclet技术,本人并未对其作深入的研究,若理解有误请指正。
XDoclet是一个开源项目,可以从这里得到他:http://xdoclet.sourceforge.net/xdoclet/ 。
XDoclet可以通过你在java源代码中的一些特殊的注释信息,自动为你生成配置文件、源代码等等,例如web、ejb的部署描述文件、为你生成struts的struts-config.xml配置文件、javascript校验等。
正如《XDoclet in Action》部分章节中文版 一文中所说的“当初,XDoclet因为可以自动生成EJB繁杂的接口和布署描述文件而声名鹊起。然而,现在的XDoclet已经发展成了一个全功能的、面向属性的代码生成框架。J2EE代码生成只是XDoclet的一个应用方面,它可以完成的任务已经远远超越了J2EE和项目文档的生成。”
目前的版本可以为web、ejb、struts、webwork、hibnaate、jdo、jmx等等生成描述文件、源码等,XDoclet提供了ant的任务target支持,完全通过ant来完成任务。
展开XDoclet的发布包,samples目录下有直接可以运行的ant脚本文件。这里以web应用target为例,说明XDoclet能为我们作些什么。
下面是samples中一个struts的action代码:
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
/**
* Simple class to test Jakarta Struts generation (Jakarta Struts 1.2 beta 2 only).
*
* @struts.action
* path="/struts/foo"
*
* @struts.action-forward
* name="success"
* path="/struts/getAll.do"
* redirect="false"
*/
public final class StrutsAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
{
return mapping.findForward("success");
}
}
注意红色的注释部分,注意执行完ant脚本后,将为你生成struts-config.xml中相关的配置项,以下是脚本执行后生成的struts-config.xml文件中的配置:
<action-mappings>
<action
path="/struts/foo"
type="test.web.StrutsAction"
unknown="false"
validate="true"
>
<forward
name="success"
path="/struts/getAll.do"
redirect="false"
/>
</action>
至此一点我们便可以了解XDoclet是如何工作的了,想想struts中的vaild配置文件、struts-config配置文件等需要我们大量的手工工作,如果再写代码的时候把相关的元数据信息写在注释里,XDoclet将为我们自动完成这些工作,当然像 @struts.action ?#162;@struts.action-forward 等这些特定的注释标签需要去查XDoclet的相关文档了,像前面说的一样,Xdoclet对目前流行的多种框架、技术都提供了相关的支持。相信在一些情况下,Xdoclet会大大提高我们的工作效率的,了解更多信息请参考Xdoclet网站http://xdoclet.sourceforge.net/xdoclet/ 。
posted @
2006-11-05 08:31 xzc 阅读(332) |
评论 (0) |
编辑 收藏
发现多功能的模板驱动的代码生成器
|
|
|
级别: 初级
Sing Li
, 作者, Wrox Press
2004 年 11 月 05 日
开放源代码的 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 是如何工作的,以帮助您理解这些特性。
XDoclet 操作
图 1 显示了 XDoclet 要求的输入和生成的输出。
图 1. XDoclet 黑盒子
您可以看到,包含嵌入式 XDoclet 标签的 Java 源代码是系统的输入。在 Apache Ant 的驱动下,XDoclet 处理输入的代码,生成的输出文本文件可以是 Java 源代码、HTML 页面、XML 文件等。为了处理输入,XDoclet 需要使用模板(保存在 .xdt 文件中)和标签处理器(用 Java 编码)。XDoclet 把模板和标签处理器打包成“模块”,不同的“模块”处理不同的问题域。
XDoclet 生成的结构模型
XDoclet 对包含嵌入式 XDoclet 标签的输入 Java 源代码进行解析,并为代码建立非常详细的结构模型。结构模型中的每个元素都代表源代码中的一个 Java 结构。图 2 显示的结构模型,揭示了 XDoclet 跟踪的代码构造和关系。
图 2. XDoclet 的解析的 Java 源代码的内部结构模型
图 2 中的结构模型跟踪类、接口、方法之类的代码构造(模型元素)。该模型还跟踪元素之间的关系,例如继承和接口实现。以内联注释的形式嵌入在源代码中的 XDoclet 标签被解析为模型元素的属性,并被跟踪。
|
通用的 Javadoc 引擎
能够理解 Java 代码结构模型的智能代码生成引擎不是什么新概念。实际上,它是 JDK 自带的 Javadoc 工具的运作方式。通过解析带有特殊 Javadoc 标签的 Java 源文件,Javadoc 工具可以为所有 Java 程序的内置结构元素(包括类、接口、字段和方法)生成 HTML 文档。Javadoc 还具有特殊 Java 语言概念方面的知识,例如继承、抽象类、存储类和修饰符。
XDoclet 的诞生,来自这样一个观察:适用于任意代码生成的 Javadoc 的通用版本,在许多编程场合下会极为有用。但是,实际的 Javadoc 源代码不是为通用的代码生成设计的,而只是为了生成 HTML 文档。由于无法重用现有代码,XDoclet 开发小组从头开始重写了引擎,并显著优化了它的性能。
|
|
深入 XDoclet
图 3 显示了 XDoclet 的内部结构,揭示了使其运行的功能块。
图 3. XDoclet 内部的功能块
|
如图 3 所示,Apache Ant 在运行的时候控制着 XDoclet 的配置和操作。XDoclet 解析输入的 Java 源代码,并在内存中生成结构模型。模板引擎通过处理一组模板和标签处理器,生成输出文件。模板和标签处理器可以是内置的,也可以是定制的。在代码生成期间,模板和标签处理器拥有对结构模型的完全访问。
XDoclet 虚假的复杂性
XDoclet 实质上就是一个通用的 Javadoc 引擎(请参阅侧栏, 通用的 Javadoc 引擎)。那么,是什么让它看起来这么复杂呢?答案在于:XDoclet 几乎从未被单独讨论过,而总是藏在其他许多复杂的技术中。图 4 显示了了围绕在 XDoclet 周围的复杂性迷雾(请参阅侧栏 为什么 XDoclet 看起来比实际的要复杂得多)。
| 为什么 XDoclet 看起来比实际的要复杂得多
Apache Ant 自动进行 Java 软件的生成过程。构建管理过程通常是生产项目中更复杂过程中的一部分。构建管理的术语和概念被集成到 Apache Ant 中,而且是理解其操作的先决条件。成熟的 Ant 脚本可能会非常复杂。Ant 的每个新版本,都会引入一些新的特性集,从而进一步增加了复杂性。这形成了 XDoclet 表面的复杂性,因为 XDoclet 需要 Ant 才能执行。 XDoclet 处理的问题领域是复杂性的另一个来源。在发布 XDoclet 的时候,XDoclet 已经可以为 EJB 组件集成、J2EE Web 容器集成、Hibernate 持久性层、Struts 框架、Java 管理扩展(JMX)等生成代码。这些问题领域中的每一个领域都有一大套该领域专用的行话和概念。从这些复杂的问题领域出来的问题,经常主导着 XDoclet 的讨论,这也提高了 XDoclet 表面的复杂性。可能是“只见森林,不见树木”。 |
|
图 4. XDoclet 的复杂耦合在图 4 中,您可以看到 XDoclet 与以下内容是紧密相关的:
- Apache Ant,它控制着 XDoclet 的操作。XDoclet 是作为一组 Ant 任务存在的,没有 Ant 则不能执行。
- 与生成文件关联的具体问题领域的一些细节。
XDoclet 本身却是惊人地简单,正如下面示例中的工作代码所示的那样。
使用 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 类
对于本例,您需要生成新的 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}
|
| 用 XDoclet 建立 XDoclet 标签
请注意在 清单 2 的模板中生成的 XDoclet @struts.form 标签。您可以用 XDoclet 在 Java 源代码中生成 XDoclet 标签,XDoclet 会在后面的操作中再次处理这些标签。在示例后面建立 structs-config.xml 的时候,XDoclet 会使用 @struts.form 标签。 |
|
在清单 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>
|
| 熟悉 Ant 脚本编写
Ant 脚本 build.xml(我已经随本文的示例代码提供了这个文件,请参阅 下载部分找到代码的链接)定义了示例应用程序使用的全部必要 Ant 目标。如果您想修改脚本,就需要熟悉 Ant。请参阅 参考资料 了解 Ant 的更多信息。 |
|
您可以参考 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.String value) { streetNumber = value; } private java.lang.Stringstreet; public java.lang.String getStreet(){ return street; } public void setStreet( java.lang.String value) { street = value; } ...... more bean properties .....}
|
为数据表单输入生成 JSP 页面
您可以用相同的 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 文件,检查文件内容,可以看到要对模板进行的替换。
| 试验示例,但是首先...
要试验本文示例中的模板化代码生成,您需要安装并测试好 Ant 和 XDoclet。本文中的代码全都基于 Ant 1.5.4 和 XDoclet 1.2.1。要生成所有的工件,并试验生成的 Web 应用程序,还需要安装 Hibernate,Struts,以及应用程序服务器,还需要能够访问关系数据库。示例基于 Hibernate 2.1.4,Struts 1.1,以及 Servlet 2.3。请参阅 参考资料 了解有关这些技术的更多信息。请一定要阅读我随源代码一起提供的 README 文件。 |
|
生成其他工件
您可以用 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 文件。
| XDoclet 中的合并点
在 XDoclet 的文档中,您会非常频繁地看到术语 合并点(merge point)和 合并文件(merge file)。合并文件是文本文件,您可以把它合并到 XDoclet 生成代码的指定位置——“合并点”上(由模板指定)。可以用合并文件来包含静态文本(例如代码片断和 XML 片断),这些文本可能很难或者不能用 XDoclet 的能力生成。例如,在示例代码的 merge/web 目录下,您会找到 struts-action.xml 文件。在代码生成期间,可以用该文件合并到 Struts 的动作映射中,构成生成的 struts-config.xml 文件的一部分。 |
|
表 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),您会节约您宝贵的时间,并在未来的开发工作中,得到数倍的回报。
下载
名字 | 大小 | 下载方法 |
---|
j-xdoclet-code.zip | | FTP |
参考资料
|
|
关于作者
posted @
2006-11-05 08:30 xzc 阅读(446) |
评论 (0) |
编辑 收藏
XDoclet 操作
图 1 显示了 XDoclet 要求的输入和生成的输出。
图 1. XDoclet 黑盒子
您可以看到,包含嵌入式 XDoclet 标签的 Java 源代码是系统的输入。在 Apache Ant 的驱动下,XDoclet 处理输入的代码,生成的输出文本文件可以是 Java 源代码、HTML 页面、XML 文件等。为了处理输入,XDoclet 需要使用模板(保存在 .xdt 文件中)和标签处理器(用 Java 编码)。XDoclet 把模板和标签处理器打包成“模块”,不同的“模块”处理不同的问题域。
XDoclet 生成的结构模型
XDoclet 对包含嵌入式 XDoclet 标签的输入 Java 源代码进行解析,并为代码建立非常详细的结构模型。结构模型中的每个元素都代表源代码中的一个 Java 结构。图 2 显示的结构模型,揭示了 XDoclet 跟踪的代码构造和关系。
图 2. XDoclet 的解析的 Java 源代码的内部结构模型
图 2 中的结构模型跟踪类、接口、方法之类的代码构造(模型元素)。该模型还跟踪元素之间的关系,例如继承和接口实现。以内联注释的形式嵌入在源代码中的 XDoclet 标签被解析为模型元素的属性,并被跟踪
深入 XDoclet
图 3 显示了 XDoclet 的内部结构,揭示了使其运行的功能块。
图 3. XDoclet 内部的功能块
如图 3 所示,Apache Ant 在运行的时候控制着 XDoclet 的配置和操作。XDoclet 解析输入的 Java 源代码,并在内存中生成结构模型。模板引擎通过处理一组模板和标签处理器,生成输出文件。模板和标签处理器可以是内置的,也可以是定制的。在代码生成期间,模板和标签处理器拥有对结构模型的完全访问。
XDoclet 虚假的复杂性
XDoclet 实质上就是一个通用的 Javadoc 引擎(请参阅侧栏,通用的 Javadoc 引擎)。那么,是什么让它看起来这么复杂呢?答案在于:XDoclet 几乎从未被单独讨论过,而总是藏在其他许多复杂的技术中。图 4 显示了了围绕在 XDoclet 周围的复杂性迷雾(请参阅侧栏为什么 XDoclet 看起来比实际的要复杂得多)。
为什么 XDoclet 看起来比实际的要复杂得多
Apache Ant 自动进行 Java 软件的生成过程。构建管理过程通常是生产项目中更复杂过程中的一部分。构建管理的术语和概念被集成到 Apache Ant 中,而且是理解其操作的先决条件。成熟的 Ant 脚本可能会非常复杂。Ant 的每个新版本,都会引入一些新的特性集,从而进一步增加了复杂性。这形成了 XDoclet 表面的复杂性,因为 XDoclet 需要 Ant 才能执行。
XDoclet 处理的问题领域是复杂性的另一个来源。在发布 XDoclet 的时候,XDoclet 已经可以为 EJB 组件集成、J2EE Web 容器集成、Hibernate 持久性层、Struts 框架、Java 管理扩展(JMX)等生成代码。这些问题领域中的每一个领域都有一大套该领域专用的行话和概念。从这些复杂的问题领域出来的问题,经常主导着 XDoclet 的讨论,这也提高了 XDoclet 表面的复杂性。可能是“只见森林,不见树木”。
|
图 4. XDoclet 的复杂耦合
在图 4 中,您可以看到 XDoclet 与以下内容是紧密相关的:
- Apache Ant,它控制着 XDoclet 的操作。XDoclet 是作为一组 Ant 任务存在的,没有 Ant 则不能执行。
- 与生成文件关联的具体问题领域的一些细节。
posted @
2006-11-05 08:29 xzc 阅读(518) |
评论 (0) |
编辑 收藏
XDoclet起步
XDoclet是一个代码生成工具,它可以把你从Java开发过程中繁重的重复劳动中解脱出来。XDoclet可以让你的应用系统开发的更加快速,而你只要付比原先更少的努力。你可以把你手头上的冗长而又必需的代码交给它帮你完成,你可以逃脱“deployment descriptor地狱”,你还可以使你的应用系统更加易于管理。而你所要做的,只不过是在你的注释里,多加一些类javadoc属性。然后,你会惊讶于XDoclet为了做到的一切。
讨论XDoclet,有一点比较容易产生混淆,那就是XDoclet不但是一系统的代码生成应用程序,而且它本身还是一个代码生成框架。虽然每个应用系统的细节千变万化(比如EJB代码生成和Struts代码生成是不一样的,而JMX代码生成又是另一番景象),但这些代码生成的核心概念和用法却是类似的。
在这一章里,我们将会看到渗透到所有XDoclet代码生成程序当中的XDoclet框架基础概念。但在之前,我们先从一个例子入手。
2.1 XDoclet in action
每一个程序员都会认识到,他们的程序永远也不会完成。总会有另一些的功能需要添加,另一些的BUG需要修正,或者需要不断的进行重构。所以,在代码里添加注释,提醒自己(或者其他的程序员)有哪些任务需要完成已成为一个共识。
如何来跟踪这些任务是否完成了呢?理想情况下,你会收集整理出来一个TODO任务列表。在这方面,XDoclet提供了一个强大的TODO生成器,来帮助你完成这个任务。这是一个把XDoclet引入项目的好机会。
2.1.1 一个公共的任务
假设你正在开发一个使用了勺子的类。
public class Matrix {
// TODO ? 需要处理当没有勺子的情况
public void reload() {
// ...
Spoon spoon = getSpoon();
// ...
}
}
理想情况下,你在下一次阅读这段代码的时候,你会处理这个“空勺子”(null spoon)的问题。但如果你过了很久才回来看这段代码,你还会记得在这个类里还有一些工作要做吗?当然,你可以在你的源码里全局搜索TODO,甚至你的集成开发环境有一个内建的TODO列表支持。但如果你想把任务所在的类和方法也标注出来的话,XDoclet可以是另一种选择。XDoclet可以为你的项目生成一个TODO报表。
2.1.2 添加XDoclet标签
为了把你的TODO项目转换成另一种更加正式的格式,你需要对代码进行一些细微的改动。如下所示:
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核心代码生成应用程序中的一个。
现在,你可以在Ant构建文件中加入一个todo目标调用这个任务来生成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 创建一个更加职业化的TODO报表
XDoclet生成的TODO报表可以有一个更加职业化的外表。报表会列出一个概览,显示在哪个包哪个类里有todo项(以及todo项的个数)。Todo项可以跟在方法、类和域上,从报表上可以清楚的区别它们。类级别的todo项会标注class,方法级别的todo项会在方法签名上标注M。构造函数和域相关的todo项也会进行相似的标注。
这个任务看起来很简单,但考虑到你所需要做的只是在注释上添加一些格式化的@todo标签,相对于那种只有人才可以理解的无格式的松散的注释,这种标签是机器可读的,也更容易编程处理。生成的输出也更容易阅读并且更加的商业化。
2.2 任务和子任务
生成todo报表,只是XDoclet可以完成的事情当中的冰山一角。当初,XDoclet因为可以自动生成EJB繁杂的接口和布署描述文件而声名鹊起。然而,现在的XDoclet已经发展成了一个全功能的、面向属性的代码生成框架。J2EE代码生成只是XDoclet的一个应用方面,它可以完成的任务已经远远超越了J2EE和项目文档的生成。
2.2.1 XDoclet 任务
到现在为止,我们一直在讨论使用XDoclet生成代码,但事实上,更确切的说法应该是,我们使用XDoclet的一个特定的任务来生成代码,比如<ejbdoclet>。每一个XDoclet任务关注于一个特定的领域,并提供这个领域的丰富的代码生成工具。
[定义:任务(Tasks)是XDoclet里可用的代码生成应用程序的高层概念。]
在XDoclet里,目前已有如下所示的七个核心任务。
<ejbdoclet>:面向EJB领域,生成EJB、工具类和布署描述符。
<webdoclet>:面向Web开发,生成serlvet、自定义标签库和web框架文件。
<hibernatedoclet>:Hibernate持续,配置文件、Mbeans
<jdodoclet>:JDO,元数据,vender configuration
<jmxdoclet>:JMX,MBean接口,mlets,配置文件。
<doclet>:使用用户自定义模板来生成代码。
<documentdoclet>:生成项目文件(例如todo列报表)
这其中,<ejbdoclet>最常用,并且很多项目也仅仅使用XDoclet来进行EJB代码生成。<webdoclet>是其次一个常用的代码生成任务。当然,在一个项目中同时使用几个XDoclet任务是可能的(并且也是推荐的),但在这些任务之间是完全独立的,它们彼此之间并不能进行直接的交流。
2.2.2 XDoclet子任务
XDoclet的任务是领域相关的,而在某个特定领域的XDoclet任务,又由许许多多紧密耦合在一起的子任务组成的,这些子任务每个都仅仅执行一个非常特定和简单的代码生成任务。
[定义:子任务(subtasks)是由任务提供的单目标的代码生成过程]
任务提供子任务执行时的上下文,并且把这些相关的子任务组织管理了起来。任务会依赖这些子任务来生成代码。在一个任务当中调用多个子任务来协同完成各种各样比较大型的代码生成任务是非常常见的。比如,在开发EJB时,你可能想要为每一个bean生成一个home接口,一个remote接口以及ejb-jar.xml布署描述符文件。这就是在<ejbdoclet>任务的上下文环境中的三个独立的代码生成子任务。
子任务可以随意的组合排列,以满足项目代码生成的需要。某个XDoclet任务包含的子任务经常会共享功能和在源文件中使用相同的XDoclet标签。这意味着当你开始一个任务的时候,你可以很容易的集成进一个相关的子任务,而不需要很大的改动。
子任务交互
让我们以<ejbdoclet>任务为例,看一下相关的子任务之间是如何进行关联的。假设你正在开发一个CMP(容器管理持久化)实体Bean。你想要使用一些<ejbdoclet>的子任务:
•<deploymentdescriptor>:生成ejb-jar.xml布署描述符文件。
•<localhomeinterface>:生成local home接口。
•<localinterface>:生成local接口。
在执行如上子任务的时候,你需要标记出你的实体Bean的CMP域。当你发布你的bean的时候,你还需要在开发商相关的布署描述符中提供某个特定的关系数据库中的特定表和列与你的CMP实体Bean的映射关系。XDoclet可以让你在原先已存在的CMP XDoclet属性基础上再加上一些关系映射属性,然后,你就可以在任务中加入一个开发商相关的子任务(例如<jboss>或者<weblogic>)来生成布署描述符文件。XDoclet提供了几乎所有的应用服务器的支持,你只需要一些初始化的小改动,就可以进行这些应用服务器相关的代码生成了。
但那只是冰山一角。你还可以使用<entitycmp>子任务为为你的bean生成一个实体bean接口的实现子类。如果你使用<valueobject>子任务来为了你的bean生成值对象,<entityemp>子任务还会为你的值对象生成方法的实现代码。
觉得不可思议了吧。可惜XDoclet没有提供<cupofcoffee>子任务,要不然我们可以喝杯咖啡,休息一下啦。
这里不是想向你介绍<ejbdoclet>所有的子任务或者<ejbdoclet>可以完成的所有代码生成功能,而仅仅是想向你展示一下任务的子任务之间是如何工作在一起的。一旦你开始并熟悉了一个XDoclet 子任务,熟悉另一个子任务会变得非常简单- 那种每个子任务都是孤立的相比,使用这种可以相互协作的子任务,开发成本会显著的降低,效果也更加的立竿见影。
2.3 使用Ant执行任务
XDoclet“嫁”给了Ant。XDoclet任务就是Ant的自定义任务,除此以外,没有其他运行XDoclet任务的方法。所幸的是,Ant已经成为了Java构建工具事实上的标准,所以这不算什么限制。事实上,反过来,XDoclet与Ant的这种“亲密”关系使得XDoclet可以参与到任何Ant构建过程当中去。
2.3.1 声明任务
XDoclet并没有和Ant一起发布,所以如果你想要使用XDoclet的话,就需要单独的下载和安装。在使用任何一个XDoclet的任务之前,你首先需要在使用Ant的<taskdef>任务来声明它。例如:
<taskdef name=”ejbdoclet”
classname=”xdoclet.modules.ejb.EjbDocletTask”
classpathref=”xdoclet.lib.path”/>
如果你熟悉Ant的话,你就会知道这段代码是告诉Ant加载<ejbdoclet>的任务定义。当然,你也可以以你喜欢的任何方式来命名这个自定义任务,但最好还是遵守标准的命名规律以免发生混淆。classname和classpathref属性告诉Ant到哪里去找实现这个自定义任务的XDoclet类。如果你想使用其他的XDoclet任务,就必须要类似这样首先声明这个任务。
一般共通的做法是,把所有需要使用的XDoclet任务都放在Ant的一个目标里声明,这样在其他的目标里如果需要使用这些任务,只要depends这个任务就可以了。你可能已经在Ant的构建文件里包含了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>
现在,任务声明好了,XDoclet“整装待发”。
2.3.2 使用任务
你可以在任何目标里使用声明好的任务。在任务的上下文环境里,可以调动相关的子任务。让我们看一个例子,这个例子调用了<ejbdoclet>任务。不要担心看不懂语法的细节,现在你只需要关心一些基础概念就可以了。
<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>
把任务想像成一个子程序运行时需要的一个配置环境(记住,子任务才是真正进行代码生成工作的)。当调用一个子任务时,子任务从任务继承上下文环境,当然,你也可以根据需要随意的覆盖这些值。在上面的例子里,因为<deploymentdescriptor>子任务生成的布署描述符文件和其他生成各种接口的子任务生成的Java源文件需要放在不同的位置,所以覆盖了destdir的属性值。布署描述符文件需要放在一个在打包EJB JAR文件的时候可以容易包含进来的地方,而生成的Java代码则需要放置在一个可以调用Java编译器进行编译的地方。需要这些子任务之间是紧密关联的,但只要你需要,你可以有足够的自主权控制任务的生成环境。
<fileset>属性同样被应用到所有的子任务。这是一个Ant的复杂类型(相对于文本和数值的简单类型),所以以子元素的方式在任务中声明。不要把它和子任务混为一谈。当然,如果你想在某个子任务中另外指定一个不同的输入文件集,你也可以在这个子任务中放置一个<fileset>子元素来覆盖它。
子任务的可配置选项远远不止这些。我们会在下一章继续介绍所有的任务和子任务,以及常用的配置选项。
2.4 用属性标注你的代码
可重用的代码生成系统需要输入来生成感兴趣的输出。一个解析器生成器也需要一个语言描述来解析生成解析器。一个商务对象代码生成器需要领域模型来知道要生成哪些商务对象。XDoclet则需要Java源文件做为输出来生成相关的类或者布署/配置文件。
然而,源文件可能并没有提供代码生成所需要的所有信息。考虑一个基于servlet的应用,当你想生成web.xml文件的时候,servlet源文件仅可以提供类名和适当的servlet接口方法。其他的信息比如URI pattern映射、servlet需要的初始化参数等信息并没有涵盖。显而易见,如果class并没有提供这些信息给你,你就需要自己手动在web.xml文件时填写这些信息。
XDoclet当然也不会知道这些信息。幸运的是,解决方法很简单。如果所需信息在源文件时没有提供,那就提供它,做法就是在源文件里加入一些XDoclet属性。XDoclet解析源文件,提取这些属性,并把它们传递给模板,模板使用这些信息生成代码。
2.4.1 剖析属性
XDoclet属性其实就是javadoc的扩展。它们在外表上和使用上都有javadoc属性一样,可以放置在javadoc文档注释里。文档注释以/**开始,*/结尾。下面是一个简单的例子:
/**
* 这是一段javadoc注释。
* 注释可以被分解成多行,每一行都以星号开始。
*/
在注释里的所有文本都被视为javadoc注释,并且都能够被XDoclet访问到。注释块一般都与Java源文件中的某个实体有关,并紧跟在这个实体的前面。没有紧跟实体的注释块将不会被处理。类(或者接口)可以有注释块,方法和域也可以有自己的注释块,比如:
/**
* 类注释块
*/
public class SomeClass {
/** 域注释块 */
private int id;
/**
* 构造函数注释块
*/
public SomeClass() {
// ...
}
/**
* 方法注释块
*/
public int getId() {
return id;
}
}
注释块分成两部分:描述部分和标签部分。当遇到第一个javadoc标签时,标签部分开始。Javadoc标签也分成两部分:标签名和标签描述。标签描述是可选的,并且可以多行。例如:
/**
* 这是描述部分
* @tag1 标签部分从这里开始
* @tag2
* @tag3 前面一个标签没有标签描述。
* 这个标签有多行标签描述。
*/
XDoclet使用参数化标签扩展了javadoc标签。在XDoclet里,你可以在javadoc标签的标签描述部分加入name=”value”参数。这个微小的改动大大增强了javadoc标签的表达能力,使得javadoc标签可以用来描述复杂的元数据。下面的代码显示了使用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();
参数化标签允许组合逻辑上相关联的属性。你可以加入描述这个类的元信息,使得这个类的信息足够生成代码。另外,程序员借由阅读这样的元信息,可以很快的理解这个类是如何使用的。(如果这个例子上的元信息你看不懂,不要担心,在第4章里,我们会学到EJB相关的标签以及它们的涵意。)
另外,请注意上面的例子中,所有的标签名都以ejb开头。XDoclet使用namespace.tagname的方式给标签提供了一个命名空间。这样做除了可以跟javadoc区别开来以外,还可以把任务相关的标签组织起来,以免任务之间的标签产生混淆。
2.5 代码生成模式
XDoclet是一种基于模板的代码生成引擎。从高层视图上来看,输出文件其实就是由解析执行各式各样的模板生成出来的。如果你理解了模板以及它所执行的上下文环境,就可以确切的认识到,XDoclet可以生成什么,不可以生成什么。如果你正在评估XDoclet平台,理解这些概念是非常重要的。要不然,你可能会错过XDoclet的许多强大的功能,也可能会被XDoclet的一些限制感到迷惑。
XDoclet运行在在Ant构建文件环境中,它提供了Ant自定义任务和子任务来与XDoclet引擎交互。任务是子任务的容器,子任务负责执行代码生成。子任务调用模板。模板提供了你将生成代码的饼干模子。XDoclet解析输入的源文件,提取出源文件中的XDoclet属性元数据,再把这些数据提供给模板,驱动模板执行。除此之外,模板还可以提供合并点(merge points),允许用户插入一些模板片断(合并文件merge files)来根据需要定制代码生成。
2.5.1 模板基础
XDoclet使用代码模板来生成代码。模板(template)是你想生成文件的原型。模板里使用一些XML标签来指导模板引擎如何根据输入类以及它们的元数据来调整代码的生成。
[定义:模板(template)是生成代码或描述文件的抽象模视图。当模板被解析的时候,指定的细节信息会被填入。]
模板一般情况下会有一个执行环境。模板可能应用在一个类环境(转换生成transform generation),也有可能应用在一个全局环境(聚集生成aggregate generation)。转换生成和聚集生成是XDoclet的两种类型的任务模式,理解它们之间的区别对于理解XDoclet是非常重要的。
当你使用XDoclet生成布置描述符文件时,你使用的是聚集生成。布置描述符文件并不仅仅只与一个类相关,相反,它需要从多个类里聚集信息到一个输入文件。在这种生成模式里,解析一次模板只会生成一个输出文件,不管有多少个输入文件。
在转换生成模式里,模板遇到每一个源文件就会解析一次,根据该文件类的上下文环境生成输出。这种生成模式会为每一个输入文件生成一个输出文件。
转换生成模式的一个很好的例子是生成EJB的local和remote接口。显然,接口是和Bean类一一相关的。从每一个类里提取信息(类以及它的方法、域、接口以及XDoclet属性等信息)转换出接口。除此以外,不需要其他的信息。
从实现里提取出接口似乎有点反向。如果你手写程序的话,一般来说会先定义一个接口,然后再写一个类来关现它。但XDoclet做不到,XDoclet不可能帮你实现一个已有接口,因为它不可能帮你生成你的业务逻辑。当然,如果业务逻辑可以从接口本身得到(比如JavaBean的get/set访问器)或者使用XDoclet属性声明好,那么生成业务逻辑代码来实现一个接口也不是不可能。但一般情况下,这样做不太现实。相比而言,提供一个实现,并描述接口与这个实现之间的关联就容易多了。
聚集生成和转换生成主要区别在它们的环境信息上。即使一个代码生成任务中生成一个Java文件,一般也不常用聚集生成,因为生成一个Java类还需要一些重要信息如类所处的包以及你想生成的类名,在这种环境下是无法提供的。如果一定要使用聚集生成的话,那就需要在另一个单独的地方提供好配置信息了。
2.5.2 模板标签
在还没见到模板长啥样子之前,我们已经比较深入的认识它了。那模板文件究竟长啥样子呢?它有点像JSP文件。它们都包含文件和XML标签,生成输出文件时XML标签会被解析,然后生成文本并显示在XML标签所处的位置上。除了以XDt为命名空间打头的XML标签会被XDoclet引擎解析以外,其余的XML标签XDoclet会忽略不管。下面的代码片断显示了XDoclet模板的“经典造型”:
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;
}
}
研究一下这个模板,你会发现,它生成的是一个类定义。这个类里定义了一个静态变量instance,并且使用一个静态方法来控制这个静态文件的访问。借助Java语法,你可以很容易的推断出那些XDoclet模板标签的目录是生成类名,虽然对于这个标签如何工作你还并不是很了解。
即使你从没打算过要自己写模板,但理解模板是如何被解析运行的还是很有必要的。迟早你会调用到一个运行失败的XDoclet任务,没有产生你所期望的输出,那么最快捷的找出原因的方法就是直接检查模板文件,看看是哪里出了问题。
让我们看一下生成静态域定义的片断:
static <XDtClass:classOf><XDtEjbFacade:remoteFacadeClass/></XDtClass:classOf>
_instance = null;
在XDoclet的眼里,这段模板代码很简单,那就是:
static <tag/> _instance = null;
XDoclet解析执行标签,如果有输出的话,把输入置回到文本里去。有些标签会执行一些运算,把输出放回到一个流里。这样的标签称之为内容标签(content tags),因为它们产生内容。
另一种类型的标签称之为BODY标签。BODY标签在开始和结束标签之间存在文本。而BODY标签强大就强大在这些文本自己也可以是一断可以由外围标签解析的模板片断。比如在上面的例子里,XDtClass:classOf标签,它们的内容就是模板片断:
<XDtEjbFacade:remoteFacadeClass/>
classOf标签解析这段模板,提取出全限制的内容,然后剃除前面的包面,只输出类名。BODY标签并不总是会解析它的内容,在做这件事之前,它们会事先检查一些外部判断条件(比如检查检查你正在生成的是一个接口还是一个类)。这里标标签称之为条件标签(conditional tags)。还有一些BODY标签提供类似迭代的功能,它的内容会被解析多次。比如一个标签针对类里的每一个方法解析一次内容。
XDoclet标签提供了许多高层次的代码生成功能,但是有时候,它们可能显得不够灵活,或者表达能力满足不了你的需要。这时候,相对于另外开发一套通用功能的模板引擎相比,你可以选择扩展XDoclet模板引擎。你可以使用更具表述能力、功能更加强大的Java平台开发你自己的一套标签。
2.6 使用合并定制
代码生成系统之所以使用的不多,主要原因就在于它们往往只能生成一些死板的、不够灵活的代码。大多数代码生成系统不允许你改动它们生成的代码;如果,如果这个系统不够灵活,你所能做到的最好的扩展就是应用继承扩展生成的代码,或者使用一些共通的设计模式(比如Proxy和Adaptor)来满足你的需要。无论如此,这都不是产生你想生成的代码的好办法。代码生成器最好能做到所生成即所得WYGIWYG(what you generate is what you get),来取代你需要花费大量的时间来粉饰生成出来的并不满足要求的代码。所以,对于代码生成器来说,支持灵活的定制,是生成能够完全满足要求的代码的前提条件。
XDoclet通过合并点(merge points)支持定制??合并点是在模板文件定义里允许运行时插入定制代码的地方。有时候,合并点甚至可以影响到全局代码的生成,不但允许你添加一些定制内容,还可以从根本上改变将要生成出来的东西。
[定义:合并点(Merge points)是模板预先定义的允许你在代码生成的运行时加入定制内容的扩展点]
让我们研究一段从XDoclet源代码里摘取出来的模板代码。在为实体Bean生成主键的模板末尾,定义了这样一个合并点:
<XDtMerge:merge file=”entitypk-custom.xdt”></XDtMerge:merge>
如果你在你的merge目录下创建了一个名为entitypk-custom.xdt文件,那么这个模板文件的内容将会在这个合并点被包含进来。你的定制可以执行高层模板可以执行的所有强大功能,可以进行所有模板可以进行的运算(包括定义自定义标签,定义它们自己的合并点)。
上面的这种合并点,为所有的类环境使用了同一个文件。当然,也可以为每一个类环境使用不同的合并文件。如果你不想定制全部的类文件,或者你不想为了某些改动而重写模板的时候,这会很有用。不管动机是什么,逐类的合并点很容易识别出来:他们会在名字里包含一个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},再根据替换后的文件名去寻找合并文件。例如,如果你有一个名为BlogFacadeBean的Bean,XDoclet会尝试寻找一个名为ejb-src-rolerefs-BlogFacade.xml的合并文件。
如果找不到这个合并文件,则这个<merge>标签的内容模板会被解析。这意味着合并点不仅可以提供定制内容,还可以在一个模板文件里定义一个替换点,当定制内容不存在的时候使用替换点里的内容。不是所有的XDoclet任务都提供了有替换内容的合并点,一般来说,它们更倾向于只提供一个简单的合并点,仅仅当合并文件存在的时候解析并导入合并文件的内容。这取决于任务的开发者觉得哪种合并点更符合他的要求。
还有一点没有介绍到的是XDoclet如何定位合并文件,每一个XDoclet任务或者子任务都会提供一个mergeDir属性,这个属性用于设置你存放合并文件的目录。
posted @
2006-11-05 08:26 xzc 阅读(368) |
评论 (0) |
编辑 收藏