将 Servlet 用于 JavaServer Faces 技术及 AJAX

作者:Mark Basler
状态:在 Early Access 版本阶段

问题描述

Java Enterprise Edition 5 平台包括 JavaServer Faces (JSF) 技术,该技术提供了成熟的可扩展的用户界面组件模型。通过使用此模型的设计,应用程序开发者可通过扩展 JSF 中包含的标准组件来方便地创建定制组件,并在应用程序中重用这些组件。本文讨论了以下策略:引入 Servlet 来处理 AJAX 请求以在 JSF 组件中添加 AJAX 支持。有关访问静态和动态资源的各种方法的详细信息,请参见“访问静态和动态资源”。有关替代解决方案和相关解决方案,请参见“结合使用 JSF 和 AJAX”。

解决方案

如果开发者要在其 JSF 组件中加入 AJAX 支持,他们可以从多个策略中进行选择。一种解决此问题的方法是,使用 Servlet 截取并实现定制组件的 AJAX 请求。定制 JSF 组件所需的资源与应用程序包打包在一起,可以直接对其进行访问。

我们将讨论使用 Servlet 将 AJAX 支持加入到 JSF 应用程序中的策略。这包括创建一个定制组件以生成所需的 Javascript ,该 Javascript 用于在客户端执行定制组件与另一个服务器端组件的 AJAX 交互,提供 PhaseListener 形式的 AJAX 请求

这种方法可用于以下情况:

这种方法自身也存在一些问题,如下所示:

出于以上原因,我们目前建议的方法是使用第三方库(“访问静态和动态资源”文章中详细介绍了这种方法)。我们仍在研究一些新出现的技术,当我们的建议发生变化时,将会对这些文章进行更新。

有关此组件功能的简单示例,请参见 standalone.jsp,其中包含单个 JSP 页面中的所有脚本和功能。


AJAX 请求流



图 1. 提交 AJAX 请求时执行的步骤

1) mouseover 事件处理程序触发用于填充和发送 XMLHttpRequest 的 Javascript 函数。请求的 URL 包含上下文根目录 (bp-jsf-example)、在 web.xml 部署描述符中配置的 Servlet 映射 (CompAServlet) 以及填充的与请求关联的查询字符串。例如:
    /bp-jsf-example/CompAServlet?itemId=test1A
2) Servlet 接收请求,检索请求的 itemId 参数以及创建/发送相应的响应。

3) 将 XML 响应发回到浏览器,并调用回调函数。

4) 分析该响应,填充弹出式组件的值并显示弹出式组件。


创建定制 AJAX 组件

创建定制 JSF 1.2 组件时,所需的对象集包括一个标记库定义文件、一个 JSF 配置文件(通常名为 faces-config.xml)、一个标记处理程序类和一个呈现器类。通常还会开发一个定制的组件类,但并非在所有情况下都需要它们。以下部分是使用 bp-jsf-example 样例应用程序的 "CompA" 定制组件作为编码示例的参考。

服务器端代码:

JSF 定制呈现器 (com.sun.javaee.blueprints.components.ui.example.CompARenderer)

在呈现通用工件时,一种跟踪是否已呈现这些工件的方法是,在请求映射中设置标志。呈现器代码检查该标志是否已存在,如果不存在,则设置该标志并呈现通用工件。如果该标志已存在,则不会重新呈现这些工件。

Map requestMap=context.getExternalContext().getRequestMap();
Boolean scriptRendered=(Boolean)requestMap.get(RENDERED_SCRIPT_KEY);

// Check to see if resource already rendered
if (scriptRendered != null && scriptRendered.equals(Boolean.TRUE)) {
return;
}

// put flag in requestMap to indicate already rendered
requestMap.put(RENDERED_SCRIPT_KEY, Boolean.TRUE);

String contextRoot=context.getExternalContext().getRequestContextPath();

// Render markup tag for the javascript file
writer.startElement("script", component);
writer.writeAttribute("type", "text/javascript", null);
writer.writeAttribute("src", contextRoot + "/compA.js", null);
writer.endElement("script");
writer.write("\n");

由于我们的组件是用于显示详细信息的弹出式组件,因此,我们需要呈现某个标记,以用于区分出实际所显示的部分。可以使用多种方法来完成此操作,一种方法是在呈现器中对标记进行固定编码,从而设置维护名称空间约定所需的值。此处,在前部添加组件 ID 以防止组件的多个实例发生冲突。组件开发者也可以使用 ResponseWriter 的 startElement 和 endElement 方法,直接在呈现器中对模板进行编码。如果要使用工具对组件进行处理(以便在 IDE 中以可视方式使用),则这可能是最佳的方法,因为每个 startElement 调用都与组件相关联。这样,工具便可以更好地跟踪属于各个组件的标记。
	
writer.write("<div class=\"bpui_compA_popTop\">");
writer.write(" <div class=\"bpui_compA_cornerTL\"><div class=\"bpui_compA_cornerTR\">");
writer.write(" <center><font color=\"white\"><b><span id=\"" + clientId + "_title\">title</span></b></font></center>");
writer.write(" </div></div>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popMid\">");
writer.write(" <table border=\"0\" style=\"width: 100%\">");
writer.write(" <tr>");
writer.write(" <td>");
writer.write(" <table border=\"0\" bgcolor=\"#ffffff\" cellpadding=\"5\" cellspacing=\"5\">");
writer.write(" <tr>");
writer.write(" <td><span id=\"" + clientId + "_message\">Value</span></td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write(" </td>");
writer.write(" </tr>");
writer.write(" </table>");
writer.write("</div>");
writer.write("<div class=\"bpui_compA_popBot\">");
writer.write(" <div class=\"bpui_compA_cornerBL\"><div class=\"bpui_compA_cornerBR\">");
writer.write(" </div></div>");
writer.write("</div>");

Java Servlet (com.sun.javaee.blueprints.components.ui.example.CompAServlet)
在 HttpServlet 中,将 HttpServletResponse 作为参数进行传入,然后构造并返回 XML 响应。由于我们的示例要求返回 XML,因此,将相应的上下文类型设置为 "text/xml"。我们还设置了状态代码,因为 AJAX 回调函数也会查找响应状态 "200",然后才会显示详细数据。请注意,我们设置了响应标头以指示不应缓存此请求/响应集。如果未设置这些标头值,某些浏览器将缓存结果,在随后包含相同 URL 的请求中,浏览器将自动多次提供相同的响应,根本不执行新的查询。在某些情况下,这可能是有益的,但对于轮询 AJAX 组件,是不希望出现缓存行为的。

	protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
String itemId=request.getParameter("itemId");

response.setHeader("Pragma", "No-Cache");
response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
response.setDateHeader("Expires", 1);
if(itemId != null) {
out.println("<response>");
// custom response code goes here
...

out.println("</response>");
} else {
out.println("<response>");
out.println("<title><![CDATA[REQUEST ERROR]]></title>");
out.println("<message><![CDATA[The query parameter 'itemId' required]]></message>");
out.println("</response>");
}
out.flush();
out.close();
}


组件的 faces-config.xml 文件
在 faces-config.xml 文件中注册且与 "CompA" 组件有关的唯一工件是,使用标准的 component-family 的定制呈现器。
	<!-- register the custom renderer for the standard component family -->
<render-kit>
<renderer>
<component-family>javax.faces.Output</component-family>
<renderer-type>CompA</renderer-type>
<renderer-class>com.sun.javaee.blueprints.components.ui.example.CompARenderer</renderer-class>
</renderer>
</render-kit>



客户端代码:

CompA 定制组件的标记将位于 JSP 页中,下面详细介绍了该标记:

	<ui:compA id="pop1" url="CompAServlet?itemId="/>

        <a href="#" onmouseover="bpui.compA.showPopup('pop1', event, 'test1A')" 
onmouseout="bpui.compA.hidePopup('pop1')" style="cursor: pointer"><b>Mouse over link to see popup (test1A)</b></a><br/>
<small><i>(With a Servlet fulfilling AJAX Request)</i></small><br/><br/>>

在填充的定制标记通过 FacesServlet 被呈现后,产生的 HTML 标记和 Javascript 代码应如下所示:
	<div id="pop1" class="bpui_compA_popup">
<div class="bpui_compA_popTop">
<div class="bpui_compA_cornerTL"><div class="bpui_compA_cornerTR">
<center>
<font color="white">
<b><span id="pop1_title">title</span></b></font>
</center>
</div></div>
</div>
<div class="bpui_compA_popMid">
<table border="0" style="width: 100%">
<tr>
<td>
<table border="0" bgcolor="#ffffff" cellpadding="5" cellspacing="5">
<tr>
<td><span id="pop1_message">Value</span></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<div class="bpui_compA_popBot">
<div class="bpui_compA_cornerBL"><div class="bpui_compA_cornerBR">
</div></div>
</div>
</div>

<script type="text/javascript">
bpui.compA['pop1']=new bpui.compA.createPopup('pop1', '/bp-jsf-example/CompAServlet?itemId=');
</script>
请注意,Javascript 代码创建新的 bpui.compA.createPopup 对象并将结果存储在 bpui.compA 名称空间的 "pop1" 属性下面。此对象是一个 Javascript 关闭,通过 bpui.compA.showPopup 函数触发弹出式组件时将检索此对象(如下所述)。


Javascript 函数:
呈现 CompA 组件时,将调用以下 Javascript 函数,它启动弹出式组件对象关闭以保留 AJAX 回调函数以及组件的标识符和 URL。回调函数进行检查以确保获得有效的响应,分析返回的 XML 数据,然后填充并显示弹出式组件。
	// create a closure to maintain the component's id and AJAX url with callback function
bpui.compA.createPopup=function(componentId, urlx) {
this.componentId=componentId;
this.urlx=urlx;

this.ajaxReturnFunction=function() {
// make sure response is ready
if (bpui.compA.req.readyState == 4) {
// make sure it is a valid response
if (bpui.compA.req.status == 200) {
// populate the popup with the info from the response
var resultx=bpui.compA.req.responseXML.getElementsByTagName("response")[0];
document.getElementById(componentId + "_title").innerHTML=resultx.getElementsByTagName("title")[0].childNodes[0].nodeValue;
document.getElementById(componentId + "_message").innerHTML=resultx.getElementsByTagName("message")[0].childNodes[0].nodeValue;;
// show popup with the newly populated information
document.getElementById(componentId).style.visibility='visible';
} else if (bpui.compA.req.status == 204){
// error, just show alert
alert("204 returned from AJAX call");
}
}
}
}



触发弹出式组件的对象使用以下 Javascript 函数来放置弹出式组件,并设置超时值以触发用于发送 AJAX 请求的函数。

	bpui.compA.showPopup=function(popupx, eventx, itemId) {
// Position popup base on event and set timeout so popup isn't flashing all the time
var xx=0;
var yy=0;
if (!eventx) var eventx=window.event;
if (eventx.pageX || eventx.pageY){
xx=eventx.pageX;
yy=eventx.pageY;
} else if (eventx.clientX || eventx.clientY) {
xx=eventx.clientX + document.body.scrollLeft;
yy=eventx.clientY + document.body.scrollTop;
}
document.getElementById(popupx).style.left= (xx + 3) + "px";
document.getElementById(popupx).style.top=yy + "px";
// make sure the popup doesn't show all the time, need to mouseover for at least 1 second.
bpui.compA.timeout=setTimeout("bpui.compA.showPopupInternal('" + popupx + "', '" + itemId + "')", 1000);
}


以下 Javascript 函数启动 XMLHttpRequest,按标识符检索弹出式组件 Javascript 对象,将 AJAX URL 与 itemId 连接在一起,设置特定弹出式组件的 AJAX 回调函数并发送请求。
	bpui.compA.showPopupInternal=function(popupx, itemId) {
// initialize the AJAX request
bpui.compA.req=bpui.compA.initRequest();
// retrieve the correct popup object that is being shown
popObject=bpui.compA[popupx];

// concatenate the itemId value to the URI
url=popObject.urlx + escape(itemId);
// set the correct popup's callback function
bpui.compA.req.onreadystatechange = popObject.ajaxReturnFunction;
bpui.compA.req.open("GET", url, true);
// send the request
bpui.compA.req.send(null);
}


参考资料


© Sun Microsystems 2006。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。