|
XML 编程 XML和J2EE的完美结合
作者:胡文青 编译
当前, Java 2平台企业版(J2EE)架构在厂商市场和开发者社区中倍受推崇。作为一种工具, 可扩展标记语言(XML)简化了数据交换、进程间消息交换这一类的事情, 因而对开发者逐渐变得有吸引力, 并开始流行起来。自然, 在J2EE架构中访问或集成XML解决方案的想法也很诱人。因为这将是强大系统架构同高度灵活的数据管理方案的结合。 XML的应用似乎是无穷无尽的, 但它们大致上可以分为三大类: ●简单数据的表示和交换(针对XML的简单API(SAX)和文档对象模型(DOM)语法解析, 不同的文档类型定义(DTDs)和概要(schemas)) ●面向消息的计算(XML-RPC(远程过程调用), SOAP协议, 电子化业务XML(ebXML)) ●用户界面相关、表示相关的上下文(可扩展样式表语言(XSL), 可扩展样式表语言转换(XSLT)) 这几类应用在J2EE架构中恰好有天然的对应:数据表示和交换功能是EJB组件模型中持久化服务(persistence services)的一部分, 基于消息的通讯由Java消息服务(JMS)API来处理, 而界面表示正是Java服务器页面(JSP)和Java Servlets的拿手好戏。 在本文中, 我们将看到当今基于J2EE的应用里, XML是如何在上述几个方面进行应用的, 以及在相关标准的未来版本中这些应用将会如何发展。 基础:数据的表示和交换 原型化的XML应用(假设有的话)的内容通常是:数据以XML格式存放, 为了进行显示、修改甚至写入某个XML文档而经常被读入到某个对象模型中。作为例子, 假定我们正处理多种类型的媒体(图品、视频、文本文档等等), 并且用下面这个简单的XML DTD来描述这些媒体的元数据: <!-- DTD for a hypothetical media management system --> <!-- Media assets are the root of the object hierarchy. Assets are also hierarchical - they can contain other assets. --> <!ELEMENT media-asset (name, desc?, type*, media-asset*, urn)> <!-- Metadata about the asset --> <!ELEMENT name (#PCDATA)> <!ELEMENT desc (#PCDATA)> <!ELEMENT type (desc, mime-type?)> <!ELEMENT mime-type (#PCDATA)> <!ELEMENT urn (#PCDATA)>以下是一个基于上述媒体DTD的XML文档, 描述了与某个课程讲座相关的内容: <?xml version="1.0" ?><!DOCTYPE media-asset PUBLIC "-//Jim Farley//DTD Media Assets//EN" "http://localhost/Articles/Sun/dtds/media.dtd"> <media-asset> <name>第14讲</name> <desc>与第14讲相关的所有内容</desc> <!-- 内容对象"lecture 14"的一套子组件 --> <media-asset> <name>讲座的幻灯片</name> <type> <desc>MS PowerPoint</desc> <mime-type>application/vnd.ms-powerpoint</mime-type> </type> <urn>http://javatraining.org/jaf/E123/lecture- 14/slides.ppt</urn> </media-asset> <media-asset> <name>讲座的视频片断</name> <type> <desc>RealPlayer streaming video</desc> <mime-type>video/vnd.rn-realvideo</mime-type> </type> <urn>http://javatraining.org/jaf/E123/lecture- 14/lecture.rv</urn> </media-asset> <!-- 讲座开始 --> <urn>http://javatraining.org/jaf/E123/lecture-14/index.jsp</urn> </media-asset> 从Web或者企业级应用的角度看, 能以这种方式访问数据真是一种福音, 因为它体现了高度的可移动性, 使我们与元数据的实际资源本身隔离。这些资源可能来自一个关系数据库系统、某种活动媒体服务器或者Web服务器上的一个静态XML文档, 等等。如果想把这些数据加载到Java应用中, 我们可以从当前众多的Java语言XML解析器中选用一个, 通过它将XML数据装入一个DOM文档, 最后遍历文档, 将所有这些数据转换到我们应用系统的对象模型中。 下面是个简单的基于DOM的解析程序, 可对上述的媒体DTD进行解析。解析器用的是 Apache Xerces: package jaf.xml; import java.util.*; import java.io.IOException; import org.w3c.dom.*; import org.xml.sax.*; // XML文档解析程序, 使用上述媒体DTD. public class MediaParser implements ErrorHandler { /** 使用Apache Xerces解析器 */ org.apache.xerces.parsers.DOMParser mParser = new org.apache.xerces.parsers.DOMParser(); /** 构造函数 */ public MediaParser() { // 告诉解析器验证并解析文档 try { mParser.setFeature( "http://xml.org/sax/features/validation", true); } catch (SAXException e) { System.out.println("Error setting validation on parser:"); e.printStackTrace(); } // 设置解析器的错误处理句柄 mParser.setErrorHandler(this); } /** 解析指定的URL, 返回找到的XML文档 */ public Document parse(String url) throws SAXException, IOException { mParser.parse(url); Document mediaDoc = mParser.getDocument(); return mediaDoc; } /** 解析指定URL的XML文档, 将内容转换成 MediaAsset 对象 */ public Collection loadAssets(String url) throws SAXException, IOException { Document doc = parse(url); Collection assets = new LinkedList(); NodeList assetNodes = doc.getElementsByTagName("media-asset"); for (int i = 0; i < assetNodes.getLength(); i++) { Node assetNode = assetNodes.item(i); MediaAsset asset = new MediaAsset(assetNode); assets.add(asset); } return assets; } /** * 错误处理代码(为简洁起见省略了) */ } MediaParser类的构造函数初始化了一个Xerces DOM解析器。parse()方法告诉解析器到哪个URL去找XML源, 然后得到结果文档并返回。loadAssets()方法调用parse()方法从某个XML源加载文档, 然后为文档中找到的每个“media-asset”节点创建一个MediaAsset对象。 以下是一个使用MediaAsset类的例子: package jaf.xml; import java.util.*; public class MediaAsset { // 资源元数据 private String mName = ""; private String mDesc = ""; private Collection mChildren = new LinkedList(); private Vector mTypes = new Vector(); private String mUrn = ""; protected MediaAsset(org.w3c.dom.Node assetNode) { // 为简洁起见省略后面代码 ... } } 因为篇幅的关系省略了MediaAsset类的详细代码, 但应用模式依然是清晰的。MediaAsset类遍历文档的节点, 当它碰到不同的子节点时, 它用子节点的内容填充自己的成员数据。如果它发现了一个嵌套的子资源节点, 它只需要创建一个新的MediaAsset对象, 然后将子资源节点的数据填充到新对象的成员数据中。 <>实现上述处理的方法数不胜数。我们还可以使用其他的解析器或解析器架构, 如Java API for XML Parsing (JAXP)。除了使用DOM模型外, 事件驱动的SAX模型也可用于解析XML。类似的程序也可用来产生XML数据——前提是允许产生新的数据对象(在本例中是MediaAsset), 它可将其相应的XML实体插入到DOM中, 然后将DOM输出到一个流中(诸如一个文件, 一个Socket, 或者一个HTTP连接...)。还有其他更高层次的标准, 可将XML映射到Java对象的过程进一步自动化(或简化)。例如, 使用XML概要(Schema)和XML绑定处理引擎, 您可以半自动地将满足某个XML 概要的XML数据转变成Java数据对象。代表性的引擎是Castor, 是由ExoLab小组管理的一个开放源代码项目的产物。上述使用Xerces DOM的简单例子仅仅是演示了这一处理过程的底层模型。上述示例表明, 在Java环境中解析或产生XML是非常方便的, 这与J2EE没有必然关联。格式化为XML的数据可以从应用程序的任何层次流入或输出, 这使得与外部系统的集成性无可限量。但我们能否以一种更为直接的方式将XML数据源集成到J2EE架构中去呢? 驾驭消息 J2EE架构包含了对JMS(Java消息服务)API的访问, 以实现面向消息的通信(J2EE 1.2.1版只需JMS API即可, 在J2EE 1.3版中JMS基本定型, 此时必须由某个兼容J2EE平台的服务器提供一个JMS API Provider)。这一类的异步交互(与之相对的是:本地或远程方法调用所代表的同步交互)被证明在某些应用环境中是非常有用的。某些时候, 交互只需要通过间接的请求或回答来实现, 即:在某些情况下, 发出消息后不可能立即收到答复, 但我们仍希望当消息发出者重新在线时, 确保他能收到答复信息。 面向消息系统的实际应用之一就是企业之间的松散集成。类似于EDI(电子文档交换)时代的文档交换, 两个企业由于业务的需要而交换消息, 此时通常不能为了使用RPC或者RMI、CORBA、DCOM之类的远程方法交互而在两者之间进行紧密集成。象JMS API这样的消息系统允许双方交换基于JMS API的消息载荷, 前提是双方在会话的时候均能提供兼容的JMS API服务。当前仍然存在的困难是:双方是否能尊从相同的格式或协议。 这正是XML大显身手的时候。XML明确地被设计来解决此类数据交换问题——灵丹妙药就是“面向消息的概要表”(Message-Oriented Communication Scheme), 实质就是基于一个双方认同的DTD或schema, 用XML格式来交换消息载荷。 JMS API支持好几种消息, 其中的TextMessage代表文本消息载荷。一个简单而有效的XML消息交换方案是, 在一端将我们的XML文档插入TextMessage, 然后在另一端用自制的XML解析程序(如前面的MediaParser)解开数据并(可选地)将其转换成Java对象。这使得我们既可以用JMS API支持的公开预订的消息模型, 也可以用JMS API支持的点对点的消息模型来发送XML消息。 上述方法有一些局限, 因为对于JMS运行时处理而言, XML的内容基本上是不透明的。例如, JMS API允许使用基于特定消息头的路由。这很容易理解, 尤其当我们希望XML消息根据其内容采取不同走向时。例如在我们的MediaAsset例子中, 我们希望公开讲座内容, 但只想把特定的内容传送给那些预订了课程的人, 或传送给那些表明可以接受某些媒体格式(如视频流)的人。为了发挥JMS API的价值, 以便实现上述基于内容的消息路由, 我们有必要从XML数据中解析出关键信息, 然后在构造标准JMS API消息头时插入这些信息。这是可行的, 但要实现XML信息我们就得额外地写很多代码(交换消息的双方均如此)。 为了在XML和JMS API之间架起桥梁, 一些厂商提供了自定义的JMS扩展, 以便直接支持XML消息机制。例如, BEA系统公司基于J2EE的WebLogic应用服务器特别为TextMessage提供了XMLMessage子类, 允许用XPath表达式来过滤XML消息。不过这是一种专有的扩展, 这要求交换消息的双方必须都能处理这类消息。 为此, Sun公司目前正在开发用于XML消息的Java API(JAXM)。其目标是提供一个高级别的标准服务, 以实现基于ebXML的消息的合成与传送。一个JAXM服务提供程序可以将这类消息映射到适当的物理消息系统(诸如JMS API)中去。 让XML看得见 将XML同Web系统的用户界面进行集成显然是一种有益的尝试。绝大多数的界面程序, 无论是基于还是不基于Web, 都是将数据进行转换, 然后用易读的格式展现给用户。用诸如XML这种“易消化”的格式存放数据将简化上述工作, 同时它还大大提高了内容的可管理性, 接下来我们就可看到这一点。不过首先要大书一笔的是, XML在Web界面层的应用得益于JSP技术的发展。 一直以来大家都希望能清晰地区分Web应用程序的表示层与底层对象模型, JSP框架诞生于这些努力之中(包括早期JHTML尝试)。JSP框架允许将Java代码嵌入到HTML内容中, 这样既可以实现动态内容, 又不必经常修改Java Servlets的代码。在页面中包含Java技术的途径是通过JSP标记(JSP Tags), 这些标记以XML风格出现。在JSP中, Java程序以代码片段、服务器端JavaBeans组件、在服务器端触发特定操作的不透明标记(标准的或自定义的)等形式存在。当某个用户通过浏览器请求JSP页面时, 一个Java应用服务器解析该JSP页面, 将其编译成一个Java Servlet, 然后执行该Servlet以产生答复页面。 一种直接将XML数据源集成到JSP的界面中去的方法是, 将XML加载到JavaBeans组件中(如同我们在MediaAsset例子中所做的), 然后在JSP中直接引用这些JavaBeans组件。 下面是一个嵌入Java代码片断的例子: <html> <head> <title>第14讲的媒体资源</title> </head> <body> <!-- 引入我们的类 --> <%@ page import="jaf.xml.*" %> <center><H3>Media Assets for Lecture 14:</H3></center> <!-- 定义一个资源对象, 以便用于显示 --> <jsp:useBean class="jaf.xml.MediaAsset" id="asset" /> <!-- 从一个先前定义的位置装载资源 --> <% MediaParser parser = new MediaParser(); Collection assets = parser.loadAssets("http://javaschool.org/jaf/E162/lecture14-assets.xml"); Iterator iter = assets.iterator(); %> <table border=0> <tr><th>Name</th> <th>Type</th><th>URN</th></tr> <% while (iter.hasNext()) { asset = (MediaAsset)iter.next(); %> <tr><td><jsp:getProperty name="asset" property="name" /></td> <td><jsp:getProperty name="asset" property="type" /></td> <td><jsp:getProperty name="asset" property="URN" /></td> </tr> <% } %> </table> </body> </html> 其中粗体部分为JSP代码片断和标记, 其余部分是标准的HTML文本。 上述程序还有一种更简洁的写法, 那就是使用自定义JSP页面标记。这样我们就可以从JSP页面中剔出代码段, 只使用JavaBeans组件和自定义的JSP标记即可。比如说, 为了去掉创建解析器、加载资源数据到集合中的那段代码, 我们可创建一个自己的标记, 由它在幕后完成这些工作。以下是例子: ... <!-- 引入我们的类 --> <%@ page import="jaf.xml.*" %> <center><H3>Media Assets for Lecture 14:</H3></center> <!-- 加载我们自定义的标记库 --> <%@ taglib uri="http://javaschool.org/taglib" prefix="media" %> <!-- 从一个先前定义的位置装载资源 --> <media:load url="http://javaschool.org/jaf/E162/lecture14-assets.xml" collectionName="assets" cursorName="asset" /> <table border=0> ... 使用自定义标记的最大好处是使我们的程序代码集中在一个地方(对Java技术而言, 一般是指在“类”中), 易于管理。这样可以将程序中对象层同界面层的集成关系定义得很清晰, 修改代码所造成的影响是可以预测和管理的。 直接将XML数据转换成Web显示内容的另一种方法是使用XSL和XSLT。在这种方案中, 将XML数据映射成HTML(或WML等)的逻辑由XSL样式表(XSL StyleSheet)来定义。样式表描述了每个特定XML数据实体应该怎样转换成界面数据实体(如HTML表格、内联标记等)。在JSP架构中, XSL转换只能应用于特定的XML数据源, 最理想的是采用一套自定义的JSP标记并引用某个XSLT处理程序。这方面的典型示例请参考java.sun.com中关于XML同JSP构架集成的白皮书。 同前面那个JSP自定义标记加XML解析器组件的方案相比, XSLT方案的伸缩性要好一些, 而且具有更好的可管理性。在这种情形下, 我们的转换逻辑是编写在一个XSL样式表中, 而不是在Java代码中。这意味着当需要修改界面时, 大多数情况下只是编辑样式表或者HTML, 代码不受影响。不过在决定选用何种方案之前, 还是要根据实际状况仔细权衡。如果选用XSLT方案, 那么就得有人负责维护这些XSL样式表(要么是负责界面的人, 要么是编写程序的人)。XSLT既像内容, 又像程序, 因此双方都不能把责任推给对方, 结果大家可能都被这不伦不类的XSLT弄得矛盾百出。从这点上考虑, 采用自定义标记并由界面开发者将其嵌入表示层的方法似乎更有吸引力, 因为这样软件工程师只考虑Java代码, 而内容工程师也只操心内容标记。 Java servlet过滤器是J2EE 1.3版在其Web层最新发布的一种Web组件。当Sevelet将请求写入某个资源或者从某个资源中读取回答信息时, 过滤器可以非常方便地转换其中的头信息和内容信息。这里所说的资源可以是一个Java servlet、一个JSP页面, 甚至一个静态Web页。过滤器的确很“酷”, 因为它允许开发人员从转换内容的代码中分离出生成内容的那部分代码, 并加以重用。当需要通过XSLT方式将XML数据转换到不同的XML应用目标时, Java servlet过滤器尤其有用。 在J2EE应用程序中使用Java servlet过滤器转换其输出, 以便兼容任何类型客户端的前景呼之欲出。servlet过滤器能够侦测到来自使用WAP协议(无线应用协议)的移动客户端的呼叫, 并且将答复内容转换成WML(无线标记语言)格式。servlet过滤器也能检测到来自iMode无线客户的呼叫, 并将其转变成cHTML(紧凑HTML)格式。当然, servlet过滤器也能够分辨出传统的HTML浏览器客户的请求, 并用正确的格式进行回复。 结束语 在J2EE 1.2.1规范中, XML“集成”仅指组件或应用程序的XML格式的部署描述。在J2EE 1.3规范中, 对XML的支持被扩展为要求具备SAX 2和DOM 2解析器, 以及在兼容J2EE的服务器平台上提供XSLT转换处理程序。您可以毋庸置疑地相信, 将来在J2EE架构中还会集成进更多的XML特性, 因为J2EE规范的定义者们会认真倾听开发者社区中对在企业级应用中使用更多XML的渴求呼声。例如, JSR(Java定义请求)处理小组中与JAXM规范相关的部分(JSR 000067)承诺在J2EE后续规范中集成进JAXM。可以预见, 在JSP架构、EJB和JDBC规范中均会有类似的变化。J2EE平台中上述组件的变革, 将使Java技术开发者目前用的XML更为规范化(以及标准化), 发挥出更大的威力。 |