在表示层上使用 XQuery

独立于特定语言实现关注点分离

Comments

在表示层上使用 XQuery 的优势

到目前为止,几乎整个 Web 开发社区都认识到 MVC 模式的好处。该模式将模型(信息内容)、视图(用户在屏幕上看到的东西)和控制器(响应用户输入或指向某个 URL 的浏览器)彼此分离开来。

在实现 MVC 模式的某些变体时,大部分开发人员通常选择 Java™ Platform, Enterprise Edition 5 (Java EE) 技术。这确实是一个健壮的解决方案,因为 Java 编程语言是先进的技术。此外,流行的框架(比如 Spring 和 Struts)为在分布式对象应用程序中使用 MVC 模式提供了便利。

但是软件开发的永恒法则就是:最佳实践总是在不断变化。如果不是这样的话,那么 Twitter 就应该用 Pascal 编写。标准也是不断演进的,因此最佳实践要么重新确立,要么完全被更新的方法和解决方案所取代。得出的结论就是,尽管 Java Eenterprise 技术不断更新,但它也可能是落后的。这也适用于其他与 Java 编程语言不同的 MVC 实现。

那么,现在的解决办法是什么呢?在视图中使用 XQuery 并在数据中使用 XML 是分离关注点的更好方式。我将对此进行解释。

XQuery 结合 XML

虽然软件开发最佳实践是不断改进的,但有些技术是持久的。其中之一就是 XML。XML 在一个层次结构中表示信息,该结构的元素和属性一般用可读性很强的语言编写。换句话说,XML 结合了两方面的优点:人类和计算机都可以理解 XML 表示的信息。

因此,XML 是软件开发社区中普遍采用的信息交换方式。当要求独立于平台和语言时,尤其适合使用 XML,比如 Web 服务。它也用于 Web 提要,因为 RSS 和 Atom 都依赖于 XML。Representational State Transfer (REST) 调用返回的结果通常是 XML 格式的。它甚至还常用于软件配置。

考虑到 XML 的普遍性,因此将它用作 MVC 模式的模型是非常有意义的。由于 XQuery 是目前用于查询 XML 的流行标准,因此用这种技术建立视图就更加有意义了。XQuery 允许进行转换,所以就更加合适。开发人员可以从 XML 文档提取必要的信息,然后根据应用程序的要求将其显示出来。

使用 XQuery 防止 “欺骗”

开发人员有时会借助一些临时修改来快速修复问题。这不一定是开发人员的错,因为有时软件的交付期限非常紧迫,因此速度比正确地解决问题更重要。但这属于另一篇文章讨论的主题。

MVC 开发人员通常直接在表示层中包含业务逻辑代码进行 “欺骗”(而不是包含到它所属的服务层中)。最常见的一个例子是在 JSP 代码中使用 scriptlet。大部分 JSP 开发人员已经倾向于这样做,或者很可能已经这样做了。

在视图中使用 XQuery 的好处是开发人员再也不能 那样做了。您不能用 XQuery 编写业务逻辑,因为 XQuery 仅关注查询和转换。力求纯正的人认为应该在另一个层上执行查询,但要记住,使用 XQuery 时可以从外部绑定变量,从而能够根据从另一个层中定义的变量有效地实现一个可调整的查询。

XQuery 不会将应用程序绑定到特定的语言

当要求独立于语言时,XML 是在软件应用程序之间交换数据的最佳方式。虽然使用完全不同的方式,PHP 应用程序也能够读取和提取 Java 应用程序所读取的那个 XML 文档。

相反,经常用作 Java Enterprise 应用程序中的模型的 Java bean 却不能被 PHP 轻松读取。类似地,Java 应用程序不能读取将属性名映射到值的 PHP 数组。在这些情况中,模型直接与编程语言绑定,这阻止了数据交换功能。

XML 不受这种限制。几乎每种语言都认识到为数据表示读取、解析和创建 XML 文档的重要性。XML 文档可以在不同的应用程序之间共享,而不需要考虑它们所使用的语言。

还记得 前面小节 提到的软件最佳实践是不断演进的吗?XML 存在的时间可能比任何现有的主流语言都要长,这意味着,将 XML 用于模型的应用程序(与被限制为某种语言的模型相反)能够更新为使用下一年偏好的技术,但受到的影响可以控制在最小的限度内。例如,如果您的模型是 Java beans,并且下一年您需要将应用程序更新到 .NET,那么您还需要根据所选择的环境语言更新模型。不过,如果您的模型是 XML,则不需要进行更新,因为 .NET 能够读取 XML 文档。

构建基于 XQuery 的 MVC 应用程序

当您在某天早晨边喝咖啡边浏览电子邮件时,您的经理突然通知您必须完成公司的 Web 站点 fishinhole.com,并且要独立于语言。他还通知您该站点不仅要独立于语言,而且还要保留 MVC 模式。然后他就得意地走开了。

由于您刚才在关注经理的口袋上的番茄酱斑点,可能已经忘记经理具体说什么了,但主要的东西还是十分清晰的。为了使用流行的设计模式(MVC),您必须重写公司 Web 站点的代码,使模型和视图不特定于某种编程语言。这下您就要仔细思考如何实现了。

实现

您知道目前没有任何应用程序是完全独立于语言的,因为所有应用程序都必须用某种语言编写。但是,如果使用正确的技术,实现经理要求的 fishinhole.com 是可行的。

根据以上的分析,您需要保留 Java 编程语言的控制器。控制器是实际向正确资源发送 URL 请求的代码。您还可以保留用 Java 编程语言编写的 Spring 框架。除了简单的 MVC 功能之外,Spring 带来很多其他功能,比如中间层访问、事务管理、面向方面的编程(AOP),依赖项注入等等。

为了独立于语言,您采用 XML 作为模型。选择 XML 的原因恰好与在表示层上使用 XQuery 的原因一样(您已在 IBM developerWorks Web 站点上的一篇文章中了解过后者)。

对视图使用 XQuery 似乎是很自然的选择。在这里,XQuery 不仅提供一种查询 XML 文档的工具,并且提供一种转换这些文档的工具。因此,最终的查询不仅返回正确的结果,并且将这些结果转换成所有浏览器都可以查看的 HTML 格式。

接下来,您需要一个 XQuery 实现。最佳的选择是来自 DataDirect 的 XQuery API for Java (XQJ) 库。说它是最佳的选择,是因为 XQJ 就像刚才的控制器一样,是用 Java 编程语言编写的。XQJ 还是用 Java 编程语言编写的最流行最健壮的 XQuery 实现。

采用什么平台呢?该 Web 站点(fishinhole.com)已经部署在 Apache Tomcat 应用服务器上。由于您考虑重写代码时要遵从 Apache Tomcat,因此决定将修改后的应用程序也部署到原来的服务器上。

业务需求

庆幸的是,您的经理没有改变业务需求。他仅要求变更底层实现。因此您只需使用 MVC 和 XQuery 实现与当前需求相同的需求。

需求很简单。该 Web 站点允许用户根据用法和配方浏览诱饵目录。用法可任选其一(撒网或拖钓)。配方也可任选其一(匙形假饵或小鱼)。当用户选择了用法和配方选项并单击 Search 按钮时,屏幕将列出符合标准的诱饵。

此外,应用程序将使用有限的页面刷新来响应用户输入,这也是一个重要的需求。目前这由 Ajax 实现完成。您知道这是市场部门的强硬要求,所以还得继续使用 Ajax 实现。

模型

如前所述,模型保留 XML 格式不变。模型需要维护每种诱饵的几项信息,包括价格、配方、可用性、运送地区和用法。因此,您的模型应该类似于 清单 1 中的文档片段。

清单 1. XML 格式的模型
<lures>
 <casting>
  <minnows brand="Yohuri" style="deep" size="3">
   <minnow color="midnight blue">
    <international-prices>
     <price denomination="dollar">3.25</price>
     <price denomination="euro">4.25</price>
     <price denomination="canadian-dollar">4</price>
     <price denomination="peso">100</price>
    </international-prices>
    <availability>
     <shipping>
      <delay>0</delay>
     </shipping>
     <regions>
      <shipping-region>United States</shipping-region>
      <shipping-region>European Union</shipping-region>
      <shipping-region>Canada</shipping-region>
      <shipping-region>Mexico</shipping-region>
     </regions>
    </availability>
   </minnow>
...
</lures>

XML 文档本身必须非常明了。直观地命名每个元素和属性,以让那些仅熟悉 XML 规范的一般读者可以直观地解析其中的信息。

控制器

控制器的目的就是提供能够使用 XQJ 进行查询的代码,并转换刚才提到的 XML 文档的格式。您知道一个使用 XQJ 作为依赖项的简单 servlet 就能实现这个目的。这样就能够尽可能少地使用 Java 代码,从而实现模型和视图独立于语言。

当用户单击 Search 按钮时,将调用 前面提到的 Ajax 函数。这个函数依赖于简单的 servlet 来获取 HTML 片段(转换后的 XML),并动态地使用该 HTML 片段替换 DIV 标记中的内容。注意,这个 Ajax 实现使用一个 POST 而不是 GET 来处理请求。

因为 Ajax 实现使用 POST 方法,所有简单的 servlet 必须实现 doPost 方法,而不是 doGet 方法。清单 2 给出了该实现。

清单 2. doPost 方法
public void doPost(HttpServletRequest request, HttpServletResponse response) 
	throws ServletException, IOException {
 try {
	PrintWriter out = response.getWriter();
		
	String usage = request.getParameter("usage");
	String configuration = request.getParameter("configuration");
				
	if (usage != null && !usage.equals("") && !usage.equals("0")) {
		if (configuration != null && 
		 !configuration.equals("") && !configuration.equals("0")) {
			String xqFile = "c:/fishinhole/searchResults.xq";
			String lures = fetchLuresByUsageAndConfiguration
			 (usage, configuration, xqFile);
			System.out.println(lures);
			out.write(lures);
		}
	}
 } catch (Exception e) {
	e.printStackTrace();
 }

简单而言,这个方法读取两个 POST 参数(configuration 和 usage),根据这些参数的值查询 XML 文档,并将转换后的 XML 文档返回给响应。这个响应的内容被原始页面中 DIV 标记的内容替换,如前所述。

首先,该方法获得 HttpServletResponse 对象的 writer。您的输出就是在这里被写入。

然后,该代码获取两个参数的值:usageconfiguration。这些参数在 Ajax 对主目录页面调用的 POST 中设置。

接下来的代码行用于防止错误。它将在用户没有为配方和用法选择有效的选项就单击 Search 按钮时跟踪 null 或空值。

最后,将指定 XQuery 文件的位置。我随后会更加详细地解释这个文件的内容。但现在,更重要的是理解这个文件包含实际解析和转换作为模型的 XML 文档的 XQuery 代码。

清单 3 中的 fetchLuresByUsageAndConfiguration 方法是一个局部方法,它依赖于 XQJ 来完成代码的查询和转换。

清单 3. fetchLuresByUsageAndConfiguration 方法
private String fetchLuresByUsageAndConfiguration(String usage, 
	String configuration, String xqFile) throws Exception {
		
	// Data source for querying
	DDXQDataSource dataSource = new DDXQDataSource();

	// Connection for querying
	XQConnection conn = dataSource.getConnection();
		
	XQExpression expression = getGenericExpression(conn); 
		
	expression.bindString(new QName("configuration"), configuration, 
		conn.createAtomicType(XQItemType.XQBASETYPE_STRING));

	expression.bindString(new QName("usage"), usage, 
		conn.createAtomicType(XQItemType.XQBASETYPE_STRING));

	FileReader fileReader = new FileReader(xqFile);
	XQSequence results = expression.executeQuery(fileReader);

	return results.getSequenceAsString(new Properties());
}

private XQExpression getGenericExpression(XQConnection conn) throws XQException {
	XQExpression expression = conn.createExpression();
		
	expression.bindString(new QName("docName"), "c:/fishinhole/fishinhole.xml",
		conn.createAtomicType(XQItemType.XQBASETYPE_STRING));

	return expression;
}

您将看到这个匿名方法的细节。现在,要注意到它返回一个 String 对象,即表示转换后的 XML 的 HTML 片段。最后,将前面一行返回的字符串写到响应中。

fetchLuresByUsageAndConfiguration 方法依赖于 3 个参数。第一个是诱饵的用法。第二个是诱饵的配方。第三个是前面提到的 XQuery 文件的位置。

首先,该代码使用一个空构造函数实例化一个 DDXQDataSource 对象。DDXQDataSource 类来自 XQJ 库,就像该方法中的大部分类一样。

接下来,将使用该数据源对象获取一个 XQConnection 对象。然后将这个对象传递给 getGenericExpression 对象。您在这个方法中将参数名 docName 绑定到表示模型的 XML 文档的字符串位置。一个带有绑定值的 XQExpression 将返回给 fetchLuresByUsageAndConfiguration 方法。

然后,再将其他两个字符串绑定到 XQExpression 对象。可以猜到,它们分别是 configuration 和 usage 值。

在构造函数中,将使用 XQuery 文件的位置(一个字符串)实例化一个标准的 FileReader 对象。有了这个 FileReader 对象之后,XQExpression 对象将使用前面的绑定值在该文件中执行查询。

该查询的结果将作为一个 String 对象返回。这个 String 对象就是前面提到的 HTML 片段。

视图

虽然改进后的 fishinhole.com 的模型是 XML 格式的,但视图却由 XQuery 决定。XQuery 不仅能够查询 XML 文档,并且还能转换文档。在这里是将 XML 转换成 HTML,以让任何 Web 浏览器都可以轻松查看。清单 4 显示了执行查询和转换的 XQuery。

清单 4. XQuery 文件(searchResults.xq)
declare variable $docName as xs:string external;
declare variable $configuration as xs:string external;
declare variable $usage as xs:string external;

<table width="100%" class="searchResultsTable">
	<tr>
		<th>Brand</th>
		<th>Color</th>
		<th>Configuration</th>
		<th>Size</th>
		<th>Usage</th>
	</tr>

{
if ($configuration = 'minnow' and $usage = 'casting') then
for $minnows in doc($docName)//casting/minnows
return
<div>
	{
	for $minnow in $minnows/minnow
	return
		<tr>
			<td>{data($minnows/@brand)}</td>
			<td>{data($minnow/@color)}</td>
			<td>{$configuration}</td>
			<td>{data($minnows/@size)}</td>
			<td>{$usage}</td>
		</tr>
	}
</div>
...

清单 4 中,首先需要了解前 3 行。您可能还记得,它们就是在控制器代码中被绑定到值的 3 个变量。

接下来就是转换的第一部分。在这里,首先从 TABLE 元素及其各个头部(header)开始执行转换。

然后是实际的 XQuery 代码。这些代码执行的查询仅返回与用户提供的用法和配方选项相匹配的结果。在这个特定的例子中,如果配方值为 minnow,用法值为 casting,将执行匹配这些值的查询并返回结果。请注意,为了节省空间,清单 4 并没有列出所有的例子。

这个特定的查询将查找所有在 casting 元素中的 minnows 元素。在每个 minnows 元素中,将查找所有 minnow 元素并提取相关的值。相关的值有 brand(一个属性)、color(一个属性)、configuration(一个绑定值)、size(一个属性)和 usage(另一个绑定属性)。

此外,还要注意,每个返回的结果都包含在 HTML 的 TD 元素或单元格中。所有单元格都包围在表行中(TR),而表行又被 DIV 元素包围。这个部分的转换将查询结果放到 HTML 表中。

目录页面

目录页面是应用程序的入口点。在这里,用户可以选择诱饵的用法和配方。当用户单击 Search 按钮时,与用户选择相匹配的结果将以容易阅读的格式显示在页面上。

这个页面的大部分内容都是用 HTML 编写的。不过,仍然需要使用 Ajax,以避免在用户单击 Search 按钮时刷新页面。Ajax 中的 j 表示 JavaScript。因此这个页面还包含 JavaScript 代码。清单 5 给出了该代码。

清单 5. 调用 Ajax 的 JavaScript 代码
function search() {
	var usageElement = document.getElementById("usageSelect");
	var configurationElement = document.getElementById("configurationSelect");
	
	var usage = usageElement.options[usageElement.selectedIndex].value;
	var configuration = 
	 configurationElement.options[configurationElement.selectedIndex].value;
	
	if (usage == "0") {
		alert("Please select a usage!");
		return;
	}
	
	if (configuration == "0") {
		alert("Please select a configuration!");
		return;
	}
	
	var parameters = {};
	parameters["usage"] = usage;
	parameters["configuration"] = configuration;
	
	var resultsDiv = document.getElementById("searchResults");
	
	ajax("./updateResults",parameters,function(success,response) {
		resultsDiv.innerHTML = response;
	});
}

当用户单击 Search 按钮时将执行 JavaScript 函数。前面几行代码分别从用户选择用法和配方的下拉列表框获取值。接下来是一些验证逻辑,它们确保用户在单击 Search 按钮之前选择了某些值。

接下来将创建一个空数组。然后使用用户从下拉列表选择的值填充该数组。在这个例子中,JavaScript 数组就像一个散列映射。这表明这些值不是与数组中的数字索引值关联,而是与实际的字符串值相关联。可以想到,字符串值分别是 usageconfiguration

然后,一个 JavaScript 变量与显示结果的 DIV 元素相关联。

最后,将调用 Ajax 代码。使用的 URL 是 ./updateResults,它被映射到前面解释的 servlet 控制器中(见 清单 2)。传入参数数组以让 servlet 能够处理用户选择的值。响应成功之后,DIV 元素的 innerHTML 属性将被由 servlet 控制器返回的经过转换的 XML(或 HTML)填充。

组织各个部分

在这里,需要注意的是配置。在这个例子中,模型和 XQuery 文件存放在 c:/fishinhole 目录中。到目前为止,查看的样例代码和随本文提供的下载代码都依赖于该位置下的这两个???件。幸运的是,这很容易根据特定的部署进行修改。

考虑到知识产权的因素,WAR 文件所需的库不包含在这个归档中。这些库是:

  • commons-logging.jar
  • ddxq.jar
  • ddxqsaxon8.jar
  • ddxqsaxon8sa.jar
  • spring.jar
  • spring-web.jar
  • spring-webmvc.jar

要了解如何获得这些文件的信息,请参见 参考资料

在 WAR 文件的 WEB-INF/lib 目录中包含以上 JAR 文件之后,就可以进行部署了。虽然详细解释 WAR 文件的部署超出了本文的范围,但这是一项简单的管理任务,您可以使用 Tomcat 中的 manager 应用程序完成部署。

部署应用程序之后,将浏览器指向这个 URL:

http://<server>:<port>/FishinHole/catalog.htm

其中 <server> 是您的 Web 服务器的 IP 地址,而 <port> 是 Web 应用服务器的端口。对于大部分本地部署,可以使用 http://localhost:8080/FishinHole/catalog.htm

开始时,目录页面并没有显示任何目录项,因为您还没有选择诱饵标准。在下拉列表中,在 Usage 列表中选择 Casting,在 Configuration 列表中选择 Minnow。然后单击 Search 按钮。首次显示可能需要几秒钟,但您最终会看到如 图 1 所示的页面。

图 1. 查询结果
目录页面的屏幕截图
目录页面的屏幕截图

您现在可以向您的经理显示这个搜索结果了,他可能会有些嫉妒您能够按照要求完成任务。他将不安地走出您的办公室,因为他知道自己完成这个工作会很困难。现在,您可以靠在椅子上继续品尝浓咖啡。

结束语

使用 XML 作为模型并使用 XQuery 作为视图是一种提供独立于语言的解决方案的强大方式,同时保留了使用 MVC 模式带来的好处。软件开发的历史表明,独立于特定软件实现的解决方案具有更强的生命力,并且从长远而言更加高效。使用 XQuery 查询和转换 XML 模型是在现代 Web 应用程序中实现以上目标的出色方式。


下载资源


相关主题

  • XQuery 简介(Howard Katz,developerWorks,2001 年 7 月,于 2006 年 1 月更新):查看 W3C 推荐的 XML 查询语言标准。本文包含了背景历史、文档路线图,以及该规范的最新进展的简介。此外,快速了解 XQuery 的表层语法的一些关键特征。
  • 在 Java 环境中使用 XQuery(Brett McLaughlin,developerWorks,2008 年 4 月):学习如何在 Java 应用程序中使用 XQuery 搜索文档。
  • Ajax:作者发现 Wikipedia 是开始学习各个科目的好资源。Ajax 也不例外。
  • Spring:下载编译和运行本文代码所需的内容。在这里可以找到 spring.jar、spring-web.jar 和 spring-webmvc.jar。
  • Apache Tomcat:下载并试用用于测试本文样例代码的应用服务器。
  • Java:获取 Sun 提供的 Java 编程语言的标准版本。
  • An XQJ Tutorial: Introduction to the XQuery API for Java:在这个教程中,找到如何下载和使用 XQJ 的说明。从这里可以获得 ddxq.jar、ddxqsaxon8.jar 和 ddxqsaxon8sa.jar。
  • Commons Logging:获取支持几种流行的日志实现的 commons-logging.jar。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Java technology, Open source
ArticleID=383665
ArticleTitle=在表示层上使用 XQuery
publish-date=04202009