4.2.6 选择合适的XML生成方式
在Web应用程序中,数据通常都持久化地存储在数据库等介质中,而不是固定的XML文档。在应用了Ajax的Web应用程序中,从XMLHttpRequest对象的响应接收到的数据,Javascript需要负起更新页面内容的责任。相对于规则的结构化的批量数据,选择以XML文档的方式返回更加合理。因此,使用XML要考虑的一个重要问题是如何按照需求生成特定的XML文档。各个Web解决方案对XML的支持不同,生成XML文档的方法自然也不同。在Java Web应用程序中,视图由JSP或者其他技术创建,这些组件动态地生成HTML代码传送给浏览器呈现,用于数据传输的XML文档也可以由JSP、JavaBean或者其他组件动态生成。但选择合适的转换方式往往能达到事半功倍的效果。下面介绍两种常用的方式,以便读者在需要的时候根据情况取舍。
1.类自行序列化成XML方式
类自行序列化成XML即每个类都实现自己的toXML()方法,选择合适的API、适当的XML结构,提供表示自己状态的元素,并由自己的成员执行递归调用,尽量便捷地生成逻辑,快速生成相应的XML文档。显然,这种方式必须要求每个类编写专门的XML生成代码,每个类只能调用自己的toXML()方法,没有额外的配置过程和复杂的构建过程。应用诸如JDOM等一些现成的API,可以大大减少开发投入,提高开发效率。例程4-8正是采用这种方式。
例程4-11是一个利用JDOM的API形成的toXml方法,不同的是,它最终返回一个Element对象,而不是将整个XML文档输出。Employ类是CRM系统中一个用来描述雇员与岗位、薪酬关系的普通Java对象,其中的Employee则是一个保存雇员信息的普通Java类。
例程4-11 Employ类的toXml()方法
public Element toXml() {
Element employee = new Element("employee");
Employee.setAttribute("name",name);
Element jobE = new Element("job").addContent(job);
employee.setContent(jobE);
Element salaryE = new Element("salary").addContent(salary);
employee.setContent(salaryE);
return employee;
}
假如将toXml()方法得到的Element对象输出的话,将得到一个XML文档片段:
<employee name="name">
<job>…</job>
<salary>…</salary>
</employee>
各个类的toXml()互相配合,按要求拼装成一个规范的XML文档。或者,直接输出到Web应用程序的用户界面。比如将例程4-11的XML通过Java Servlet输出给浏览器显示,其代码如例程4-12所示。
例程4-12 Servlet输出XML文档
public void doGet(HttpServletRequest req,HttpServletResponse res)
throws IOException,ServletException {
Employ employ = new Employ();
Element resElement = employ.toXml();
Document resDocument = new Document(resElement);
res.setContentType("application/xml");
new XMLOutputter().output(resDocument,res.getWriter());
}
JDOM提供了现成的API,使得序列化成XML的工作更加简单,我们只需要把toXML()外面包装一个Document,然后使用XMLOutputter把文档写入servlet就可以了。toXml()允许递归调用其子类的toXML()方法,以便生成包含子元素的XML文档。
采用类自行序列化成XML的方式,所有需要输出XML文档的Java类都实现自己的toXml()方法,还要确保标记配对以及处理实体编码,而且存在数据模型与视图耦合的问题,即要么为每个可能的视图编写独立的toXML()方法,要么心甘情愿地接收冗余的数据,一旦数据结构或者文档发生改变,toXML()就要做必要的修改。
从设计的角度来看,这是数据模型与视图生成耦合的经典问题。每个bean只能用一种途径序列化自己,一成不变的方式意味着Ajax交互最终要交换它们不需要交换的数据,因此造成客户端代码要从文档中找到需要的信息更加困难,而且也会增加带宽的开销和客户端的XML解析时间。这种耦合的另一个后果就是XML的语法不能脱离Java类独立变化。例如,对顾客文档的方案做修改,可能会影响多个Java类,造成它们也不得不做修改和重新编译。
2.XML与Java类绑定方式
诸如XMLBeans等开源框架提供了相应的Java API,可以简化XML文档到Java对象的捆绑过程,可以在Java对象和XML文档之间执行双向会话。这些框架封装了XML处理的全部工作,这意味着应用程序代码只需要处理普通的Java类,提供有用的辅助功能,例如文档验证。这些框架采用了两种不同的方式:代码生成和对象到XML映射。
使用代码生成的框架包括XMLBeans、JAXB、Zeus和JBind。Castor 也能使用这项技术。这类框架的起点是描述文档数据类型的 XML 方案。使用框架提供的工具,就可以生成代表这些方案定义类型的Java类。最后,用这些生成的类编写应用程序,表示自己的模型数据,并通过框架提供的一些辅助机制把数据序列化成XML。
如果应用程序要使用大型的XML语法,那么代码生成方式是个很好的方法。在数十个类上编写定制XML序列化代码的可伸缩性问题由此消除。另外,也不再需要定义自己的JavaBean。框架生成的Java类通常非常符合 XML 的结构,所以很难对它们进行编码。而且,生成的类变成哑数据容器,因为一般不能向它们添加行为。一般来说,在应用程序代码中要做些妥协,才能很好地处理方案生成的类型。另一个缺陷是如果修改方案,会造成生成的类也要修改,所以也就会对围绕它们编写的代码带来相应的影响。这种类型的XML绑定框架在数据拆解时最有用(例如,使用XML文档并把它们转化成Java对象)。
XMLBeans是代码生成框架的代表。它提供一种机制,可以将XMLBeans用于XML绑定。与其他只支持W3C XML Schema规范的某个子集的数据绑定技术不同,XMLBeans支持完整的范式。开发人员通过下列两个步骤,便可以使用Java类访问和操纵XML文档中包含的数据:
第一步:XMLBeans编译器生成XML模式的对象表示。这种对象表示是一组普通的Java类和接口,用于表示模式的结构和约束。
第二步:符合上述模式的实际XML文档被绑定到第一步生成的Java类和接口的实例。绑定过程需要用XMLBeans的API,以面向对象的方式访问真正的XML实例文档中的数据。
一旦XMLBeans编译器生成了和模式对应的一般Java类和接口,任何符合该模式的XML实例文档都可以使用这些类和接口绑定。XMLBeans比传统的解析更进了一步,因为用户不再需要进行以下操作:
导航内存中的数据树的每个节点。
编写回调方法,从XML文档中提取信息。
XMLBeans目前可用的最高版本是V2.1.0,有兴趣的读者可以自行从http://xmlbeans. apache.org/下载到可用版本以及相关文档。
采用映射方式的框架包括Castor和Apache Commons Betwixt。映射通常是比代码生成更灵活和更轻量的解决方案。首先,可以像通常一样编写JavaBean,包括任何行为以及任何自己喜欢的方便的方法。然后,在运行的时候,调用框架中基于内省的编排器,并根据对象成员的类型、名称和值生成XML文档。通过定义类的映射文件,可以覆盖默认的绑定策略,并就类在XML中的表示方式对编排器提出建议。
这种方法是在可伸缩性与灵活性之间的一个良好折中。可以按照自己喜欢的方式编写Java类,编排器负责处理XML。虽然映射定义文件编写起来简单,可伸缩性也足够好,但是映射规则最多只能改变标准的捆绑行为,而且在对象结构和它们的XML表示之间总要残留一些耦合。最终,可能不得不在Java表示或XML格式之间任选一个做些折中,才能让映射方法起作用。
所有的XML绑定API都具有手工序列化技术的一个主要不足:模型和视图的耦合。被限制为一个类型一个XML表示,就意味着在网络上总要有冗余数据传输。更严重的问题是,在要求客户端代码使用专门视图时,客户端代码却无法得到它,所以可能要费力地处理给定对象图的一成不变的视图。在传统的Web应用程序开发中,通常采用页面模板系统把视图生成与控制器逻辑和模型数据干净地分离。这种方法在Ajax场景中也会有帮助。
3.页面模板生成方式
任何通用目的的页面模板技术都可以用来生成XML,从而使Ajax应用程序可根据自己的数据模型生成任何XML响应文档。额外收获是:模板可以用简单的、表现力强的标记语言编写,而不是用一行行的Java代码编写。例程4-13是一个采用Struts标记库编写的文档,输出一个代表雇员与岗位、薪酬关系的employees.xml:
启动Eclipse3.1.1,在com.ajaxlab.ajax包下新建一个Employee类,其中包含name、job、salary三个String类型的属性及其对应的get、set方法,具体的代码如例程4-13所示。
例程4-13 Employee.java
package com.ajaxlab.ajax;
public class Employee {
private String name;
private String job;
private String salary;
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public String getSalary() {
return salary;
}
public void setSalary(String salary) {
this.salary = salary;
}
}
在sample4_10.jsp中,初始化三个Employee对象并设置其属性,然后调用Struts的<logic:iterate>、<bean:write>等标记将对象信息生成XML文档。要使sample4_10.jsp正常编译运行,须将struts-logic.tld、struts-html.tld、struts-bean.tld拷贝到{APPLICATION_HOME} \webapps\WEB-INF目录下,将struts.jar、struts-legacy.jar、commons-beanutils.jar三个jar包拷贝到{APPLICATION_HOME}\webapps\WEB-INF\lib目录下。这三个tld和jar文件都可以在博文视点的网站的资源下载文件中得到,均是Struts运行的必要文件。其显示效果如图4-9所示。也可以使用JSP的循环和输出语句代替代替例程4-14中的<logic:iterate>、<bean:write>标记。
例程4-14 sample4_10.jsp
<%@ page
contentType="application/xml; charset=gb2312" import="com.ajaxlab. ajax. Employee"%>
<%@ page import="java.util.Collection,java.util.ArrayList"%>
<?xml version="1.0"?>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%
Employee em1 = new Employee();
em1.setName("J.Doe");
em1.setJob("Programmer");
em1.setSalary("32768");
Employee em2 = new Employee();
em2.setName("A.Baker");
em2.setJob("Sales");
em2.setSalary("70000");
Employee em3 = new Employee();
em3.setName("Big Cheese");
em3.setJob("CEO");
em3.setSalary("100000");
Collection employees = new ArrayList();
employees.add(em1);
employees.add(em2);
employees.add(em3);
pageContext.setAttribute("employees",employees);
%>
<employees>
<logic:iterate name="employees" id="employee">
<employee name="<bean:write name='employee' property='name'/>">
<job><bean:write name="employee" property="job"/></job>
<salary><bean:write name="employee" property="salary"/> </salary>
</employee>
</logic:iterate>
</employees>

图4-9 页面模板生成XML文档
采用页面模板生成XML方式,需要为每个需要的数据模型建立一个对立的JSP文件,用来生成符合规范的XML文档,而不能仅仅在类的toXML()方法中组织对象图来实现。不过,倒是可以更加方便地确保标记匹配、元素和属性的顺序正确以及XML实体的正确转义。







