简化 Ajax 和 Java 开发,第 1 部分: 用 JSP 标记文件动态生成 JavaScript 代码
基于简单开发的 JSP 标记文件构建可重用的 Ajax 和 Java 组件
Andrei Cioroianu, 高级 Java 开发人员和顾问, Devsphere
转自http://www.ibm.com/developerworks/cn/web/wa-aj-simplejava1/index.html
简介: 很多 Web 开发人员都经常抱怨说 Java? EE 太复杂、构建新的 Web 组件太难、定制现有的组件没有预想的那样简单,并且即便是很小的更改都需要重新启动应用程序。本系列给出了针对这些问题的解决方案,即采用代码生成器、约定、脚本语言和先进的 JavaServer Pages ? (JSP) 特性。在本文中,您将了解如何基于 JSP 标记文件构建可重用的 Ajax 和 Java 组件,而这些 JSP 标记文件很容易开发和部署。更改之后,JSP 标记文件会由 Java EE 服务器自动重编译,而无须重启应用程序。此外,您还能完全控制所生成的代码,并能轻松地定制这些轻量级组件,因为它们使用的是 JSP 语法。
本系列含 4 部分,展示了一种基于 JSP 的技术,用以生成 JavaScript 代码、显著减少需要手动编写的代码量,本文是第 1 部分。本文的示例应用程序展示了如何生成 JavaScript 函数来发送 Ajax 请求和处理 Ajax 响应。如果想要轻松地更改 Ajax 代码,可以将这里讨论的简单技巧应用到实际的应用程序中。本文更宽泛的目标是展示如何使用 JSP 标记文件针对具体需求生成 JavaScript 代码,而非只是 Ajax 例程。
使用框架和代码生成器
如果您很幸运地找到了一种能满足您需要的组件或框架,那么就请使用它吧。如果没有找到也没关系,因为您总是可以开发自己的解决方案,也可以定制现有的一段代码。不管是哪种情况,一种很好的做法是 “参数化” 代码并将其放入一个可重用的库,而非将参数硬编码到您的代码里。不过有时候,实现泛型并不实际,因为它会使开发变得复杂,而非简化。在将泛型代码放入可重用组件或框架时,可以考虑使用代码生成器来更有效地生成特定的代码。
在开发的过程中避免复制 & 粘贴
假设,您需要应用程序使用 Ajax 请求站点上的某些信息,最快的(当然不是最好的)方法是找到一些如清单 1 这样的免费代码、更改 URL 并将这些代码粘贴到 Web 页面。很多开发人员都会这么做,但这种做法会导致巨大的维护问题。如果应用程序具有数百个页面,最后的结果将是出现大量像清单 1 中的 getInfo() 这样的函数。不好的一面是每次需要进行添加或更改(比如实现 Ajax 请求的错误处理)时,您都必须要手动修改所有页面并重新测试它们。好的一面是您可以通过使用库、框架和代码生成器,很容易地避免这个维护问题。
清单 1. Ajax 函数
function getInfo(country, city) { var request = null; if (window.ActiveXObject) request = new ActiveXObject("Microsoft.XMLHTTP"); else if (window.XMLHttpRequest) request = new XMLHttpRequest(); else return; var url = "CityInfo.jsp?country=" + escape(country) + "&city=" + escape(city); request.open("GET", url, true); function processResponse() { if (request.readyState == 4) { if (request.status == 200) { // ... } } } request.onreadystatechange = processResponse; request.send(null);}function xhr(url, paramNames, paramValues, method, callback) { // send Ajax request ...}function getInfo2(country, city) { function processResponse(request) { // process Ajax response ... } xhr("CityInfo.jsp", ["country", "city"], [country, city], "GET", processResponse);}<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>...<script type="text/javascript"> <da:xhr function="getInfo3(country, city)" url="CityInfo.jsp" method="GET"> // process Ajax response ... </da:xhr></script>...<button ... onclick="getInfo3(...)">Get Info</button>
<script src="DynamicJavaScript.jsp" type="text/javascript"></script>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:if test="${empty cachedCode}"> <c:set var="cachedCode" scope="application"> alert("Cached Code"); </c:set></c:if>${applicationScope.cachedCode}<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %><html><head><title>Ajax Demo</title> <script src="ajax.js" type="text/javascript"> </script> <script src="encode.js" type="text/javascript"> </script> <script type="text/javascript"> <da:xhr function="getInfo(country, city)" method="GET" url="CityInfo.jsp" sync="false" json="true" cpr="true"> <da:innerHTML id="info" value="json.info" encode="true"/> </da:xhr> function getInfo2(form) { var country = form.country.options[form.country.selectedIndex].text; var city = form.city.value; getInfo(country, city); } </script></head><body> <form name="data"> Country: <select name="country" size="1"> <option>Canada</option> <option>UK</option> <option selected>USA</option> </select> City: <input name="city" size="20"> <button type="button" onclick="getInfo2(this.form)">Get Info</button> </form> <div id="info"></div></body></html>var getInfoRequest = null;function getInfo(country, city) { if (getInfoRequest) closeRequest(getInfoRequest); var url = "CityInfo.jsp"; url += '?'; url = appendParam(url, "country", country); url = appendParam(url, "city", city); var request = openRequest("GET", url, true); getInfoRequest = request; if (request == null) return null; function processResponse() { if (request.readyState == 4) { if (request.status == 200) { eval(request.responseText); document.getElementById("info").innerHTML = htmlEncode(json.info); } else { httpError(request); document.location = url; } } } request.onreadystatechange = processResponse; sendRequest(request, null); return request;}function getInfo(country, city) { ... var url = "CityInfo.jsp"; var body = ""; body = appendParam(body, "country", country); body = appendParam(body, "city", city); var request = openRequest("POST", url, true); ... sendRequest(request, body); return request;}<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %><da:noCache/>json = { country: <da:jstring value="${param.country}"/>, city: <da:jstring value="${param.city}"/>, info: <da:jstring>Info on ${param.city}, ${param.country}</da:jstring>}json = { country: "UK", city: "London", info: "Info on London, UK"}<% response.setHeader("Cache-Control", "no-cache"); %><%@ attribute name="value" required="false" rtexprvalue="true" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:if test="${empty value}"> <jsp:doBody var="value"/></c:if><% String value = (String) jspContext.getAttribute("value"); out.write('"'); int len = value.length(); for (int i = 0; i < len; i++) { char ch = value.charAt(i); switch (ch) { case '\\': out.write("\\\"); break; case '\n': out.write("\\n"); break; case '\r': out.write("\\r"); break; case '\t': out.write("\\t"); break; case '"': out.write("\\""); break; default: { if (' ' <= ch && ch <= '~') out.write(ch); else { out.write("\\u"); for (int j = 3; j >= 0; j--) { int k = (((int) ch) >> (j << 2)) & 0x0f; out.write((char) (k < 10 ? k + 48 : k + 55)); } } } } } out.write('"');%>function openRequest(method, url, async) { var request = null; if (window.ActiveXObject) request = new ActiveXObject("Microsoft.XMLHTTP"); else if (window.XMLHttpRequest) request = new XMLHttpRequest(); if (request) request.open(method, url, async); return request;}function sendRequest(request, body) { if (body) request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.send(body);}function closeRequest(request) { request.onreadystatechange = function() { }; request.abort(); delete request;}function httpError(request) { alert("Http Error: " + request.status);}function htmlEncode(value) { return value ? value.replace(/&/g, "&") .replace(/</g, "<").replace(/>/g, ">") : "";}function attrEncode(value) { return htmlEncode(value).replace(/"/g, """);}function urlEncode(str) { return str ? escape(str).replace(/\+/g, "%2B") : "";}function isArray(a) { return a.sort ? true : false;}function appendParam(url, name, value) { if (isArray(value)) { for (var i = 0; i < value.length; i++) url = appendParam(url, name, value[i]); } else { if (url && url.length > 0) { if (url.charAt(url.length-1) != '?') url += "&"; } else url = ""; url += urlEncode(name) + "=" + urlEncode(value); } return url;}<%@ attribute name="function" required="true" rtexprvalue="true" %><%@ attribute name="method" required="false" rtexprvalue="true" %><%@ attribute name="url" required="true" rtexprvalue="true" %><%@ attribute name="sync" required="false" rtexprvalue="true" type="java.lang.Boolean" %><%@ attribute name="json" required="false" rtexprvalue="true" type="java.lang.Boolean" %><%@ attribute name="cpr" required="false" rtexprvalue="true" type="java.lang.Boolean" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="dau" tagdir="/WEB-INF/tags/dynamic/ajax/util" %><c:set var="method" value="${empty method ? 'GET' : fn:toUpperCase(method)}"/><c:set var="reqVarName" value="${fn:trim(fn:substringBefore(function, '('))}Request"/><c:if test="${cpr}">var ${reqVarName} = null;</c:if>function ${function} { <c:if test="${cpr}">if (${reqVarName}) closeRequest(${reqVarName});</c:if> var url = "${url}"; <c:if test="${method == 'GET'}"> url += '?'; <dau:appendParams jsVarName="url" function="${function}"/> </c:if> <c:if test="${method == 'POST'}"> var body = ""; <dau:appendParams jsVarName="body" function="${function}"/> </c:if> var request = openRequest("${method}", url, ${!sync}); <c:if test="${cpr}">${reqVarName} = request;</c:if> if (request == null) return null; function processResponse() { if (request.readyState == 4) { if (request.status == 200) { <c:if test="${json}">eval(request.responseText);</c:if> <jsp:doBody/> } else { httpError(request); <c:if test="${method == 'POST'}">url += '?' + body;</c:if> document.location = url; } } } request.onreadystatechange = processResponse; <c:if test="${method == 'GET'}">sendRequest(request, null);</c:if> <c:if test="${method == 'POST'}">sendRequest(request, body);</c:if> return request;}<%@ attribute name="jsVarName" required="true" rtexprvalue="true" %><%@ attribute name="function" required="true" rtexprvalue="true" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><c:set var="paramList" value="${fn:substringBefore(fn:substringAfter(function, '('), ')')}"/><c:forEach var="paramName" items="${paramList}"> <c:set var="paramName" value="${fn:trim(paramName)}"/> ${jsVarName} = appendParam(${jsVarName}, "${paramName}", ${paramName});</c:forEach><%@ attribute name="id" required="true" rtexprvalue="true" %><%@ attribute name="value" required="false" rtexprvalue="true" %><%@ attribute name="encode" required="false" rtexprvalue="true" type="java.lang.Boolean" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:if test="${empty value}"> <jsp:doBody var="value"/></c:if><c:if test="${encode}"> document.getElementById("${id}").innerHTML = htmlEncode(${value});</c:if><c:if test="${!encode}"> document.getElementById("${id}").innerHTML = ${value};</c:if>