原文位址: http://www.iteye.com/topic/587879
現在JavaWeb領域,MVC架構越來越多,比較出名的有Struts、Struts2、SpringMVC、WebWork等。而Ajax,作為一種與特定的動态Web程式設計語言(如Java、C#、PHP)無關的技術,也已經被引入到了Java MVC架構的各家各戶。而這些MVC架構,歸根到底,都是對Servlet技術的封裝。同時,支援Ajax的JavaScript架構(or類庫)也越來越多,出名的如Jquery、Ext、Prototype、DWR等,而它們實作異步傳輸功能還是離不開JavaScript中的XMLHttpRequest這個對象。好,轉入正題吧。
Ajax通過XMLHttpRequest對象實作異步傳輸,那我們首先要擷取這個對象。由于浏覽器的差異,為了相容各種常用的浏覽器,先寫一個初始化XMLHttpRequest對象的方法,代碼如下:
Js代碼

- function doGet(url, data, callback) {
- var url = url;
- if(url.indexOf("?") == -1) {
- url = url + "?" + data;
- } else {
- url = url + "&" + data;
- }
- initXmlHttp();
- xmlHttp.onreadystatechange = callback; //注冊回調函數
- xmlHttp.open("GET", url, true); //設定連接配接資訊
- xmlHttp.send(null);
- }
- function doPost(url, data, callback) {
- initXmlHttp(); //初始化
- xmlHttp.onreadystatechange = callback; //注冊回調函數
- xmlHttp.open("POST", url, true);
- xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- xmlHttp.send(data);
- }
- function callback() {
- //判斷對象的狀态是否互動完成
- if(xmlHttp.readyState == 4) {
- //判斷http的互動是否成功
- if(xmlHttp.status == 200) {
- //擷取伺服器傳回的純文字資料
- var responseText = xmlHttp.responseText;
- //擷取伺服器傳回的XML格式資料
- //var responseXml = xmlHttp.responseXML;
- //Alert從伺服器端傳回的資訊
- window.alert(responseText);
- }
- }
- }
對上面的代碼,在這裡解析一下:XMLHttpRequest對象的請求狀态(readyState)有0、1、2、3、4,其中,0表示未初始化,1表示open方法成功調用,2表示伺服器應答用戶端請求,3表示互動中,HTTP頭資訊已經收到,但響應資料還沒有接收,4表示資料接收完成。我們通過“xmlHttp.onreadystatechange = callback;” 來設定如果XMLHttpRequest對象的請求狀态發生改變了,則會執行回調函數callback。我們可以看到,在callback方法體中,我們隻關心readyState==4(互動完成)的情況,再擷取從伺服器端傳回的狀态碼status,常見的狀态碼有:200表示互動成功,404表示頁面沒找到,500表示伺服器處理錯誤等。接着,通過XMLHttpRequest的responseText屬性得到從伺服器端傳回的文本資料,或者通過responseXML屬性獲得XML格式的資料。
在上面的代碼中,doGet方法和doPost方法都有參數”data”,它由XMLHttpRequest負責從用戶端傳送到伺服器端,對于Get方法,附在URL尾部,例如:member.jsp?name=xxx&sex=male。對于Post方式,可調用XMLHttpRequest的send方法發送。data的資料形式比較靈活,可以是普通的參數格式、XML格式,JSON格式或者是其他格式,隻要你能發送過去,伺服器端就有辦法将你解析出來。在這裡,我們降低難度,就用最簡單的參數格式,即
key1=value1 & key2=value2 & key3=value3 & ……
我們都知道,HTTP協定的Get方式傳輸資料,是通過把這些key-value串附到URL後面的,也就是我們隻要點表單的送出按鈕,就可以看到位址欄後面會多了一串key-value,代表表單裡各輸入框的名和值。然後,我們要做異步發送資料,就不能用表單的自動送出了,也就是說,得自己一個一個擷取到各輸入框的資料,然後再一個一個拼成上面的key-value串再發送。有沒有一種簡單的辦法來組織這些資料呢?大家看到key-value是否會想到Java中的什麼類?請看下面代碼,我用JavaScript寫了一個Map類(JavaScript中的“function”可以看作是方法,也可以看作是面向對象的“類”),就是類似于Java中我們常用的Map接口。
Java代碼

- function Map(){
- //key集
- this.keys = new Array();
- //value集
- this.values = new Array();
- //添加key-value進Map
- this.put = function(key, value){
- if(key == null || key == undefined)
- return;
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- //如果keys數組中有相同的記錄,則不覆寫原記錄的值
- if(this.keys[i] == key)
- this.values[i] = value;
- }
- this.keys.push(key);
- this.values.push(value);
- };
- //擷取指定key的value
- this.get = function(key){
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.keys[i] == key) {
- return this.values[i];
- } else {
- continue;
- }
- return null;
- }
- };
- //移除指定key所對應的map
- this.remove = function(key) {
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.keys[i] == key) {
- while(i < length - 1) {
- this.keys[i] = this.keys[i+1];
- this.values[i] = this.values[i+1];
- i ++ ;
- }
- //處理最後一個元素
- this.keys.pop();
- this.values.pop();
- break;
- }
- }
- };
- //是否包含指定的key
- this.containsKey = function(key) {
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.keys[i] == key) {
- return true;
- }
- }
- return false;
- };
- //是否包含指定的value
- this.containsValue = function(value) {
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.values[i] == value) {
- return true;
- }
- }
- return false;
- };
- //包含記錄總數
- this.size = function() {
- return this.keys.length;
- };
- //是否為空
- this.isEmpty = function() {
- return this.size() == 0 ? true : false;
- };
- //清空Map
- this.clear = function() {
- this.keys = new Array();
- this.values = new Array();
- };
- //将map轉成字元串,格式:key1=value1,key2=value2
- this.toString = function() {
- var length = this.size();
- var str = "";
- for(var i = 0 ; i < length ; i ++ ) {
- str = str + this.keys[i] + "=" + this.values[i];
- if(i != length-1)
- str += ",";
- }
- return str;
- };
- }
代碼比較長,有些方法在本例中可能用不到,但也寫出來了,或者在其他地方可能有用吧。當我們使用這個Map類來存儲HTTP的參數時,發覺有幾個不妥的地方:一是put方法,在Java的Map接口中,是不允許有重複的key存在的,而在JavaScript中作為傳輸參數的載體時,很多時候會出現多個同名的key的,例如處理表單的checkbox時,同一個name的有幾個checkbox,構成一個複選框組,組織參數時就形如“key=value1&key=value2”,故put方法必須改。也是由于這個原因,get方法和remove方法也要改。二是toString方法,key=value對,不是用“,”号隔開的,而是用“&”号,故toString方法也須改。而有時候想想,如果把Map類改了,如果其他地方要用到的話,是不是還是改回來,與其改來改去的,不如繼承它,重寫put、get、remove和toString方法。好主意,代碼如下:
Js代碼

- function ParamMap() {
- //繼承Map類
- Map.call(this);
- //重寫put方法,允許多個同名key存在
- this.put = function(key, value){
- if(key == null || key == undefined)
- return;
- this.keys.push(key);
- this.values.push(value);
- };
- //重寫get方法,傳回values數組
- this.get = function(key) {
- var results = new Array();
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.keys[i] == key)
- results.push(this.values[i]);
- }
- return results;
- };
- //重寫remove方法
- this.remove = function(key) {
- var length = this.size();
- for(var i = 0 ; i < length ; i ++ ) {
- if(this.keys[i] == key) {
- while(i < length - 1) {
- this.keys[i] = this.keys[i+1];
- this.values[i] = this.values[i+1];
- i ++ ;
- }
- //處理最後一個元素
- this.keys.pop();
- this.values.pop();
- }
- }
- };
- //重寫toString方法, 轉成XMLHttpRequest.send(ajaxString)方法的參數格式的字元串,
- //形如:key1=value1&key2=value2
- this.toString = function() {
- var length = this.size();
- var str = "";
- for(var i = 0 ; i < length ; i ++ ) {
- str = str + this.keys[i] + "=" + this.values[i];
- if(i != length-1)
- str += "&";
- }
- return str;
- };
- }
怎麼使用這個ParamMap類呢,且看下面的示例代碼:
Js代碼

- var username = document.getElementById("username").value;
- var password = document.getElementById("password").value;
- var sex = document.getElementById("sex").value;
- var map = new ParamMap();
- map.put("username", username);
- map.put("password", password);
- map.put("sex", sex);
- doGet("test/register", map.toString(), callback);
- doPost("test/register", map.toString(), callback);
Js代碼

在JavaScript中,用來擷取HTML結點的方法,常用的有如下方法:
Js代碼

- Node document.getElementById("username") //根據标簽的id
- NodeList document.getElementsByName(“username”) //根據标簽的name
- NodeList document.getElementsByTagName("input") //根據标簽的标簽名
我們注意到在除了getElementById是傳回Node對象外,其他兩個方法都是傳回NodeList對象,相當于Node數組。在Ajax應用中,根據ID來擷取節點,很多時候十分友善,如擷取text、password、hidden、textarea類型的值,但有時候并不那麼友善,如處理checkbox、radio(不允許多個同名的id)。況且有許多情況下,開發者是由“非Ajax”轉成Ajax應用的。在還沒有引進Ajax的時候,表單傳值都是根據輸入域的name的值來區分的,不管是Get方式還是Post方式,在伺服器端(這裡指Java Servlet)擷取資料的代碼如下:
Java代碼

- String HttpServletRequest.getParameter("keyName"); //用于單值表單域
- String[] HttpServletRequest.getParameterValues("keyName"); //用于多值表單域
所謂的“單值表單域”就是上面提到過的type為text、password、hidden的input或者textarea等,而“多值表單域”是指checkbox。其實,也不盡然,如單值表單域有時候也可以是多值表單域,如我們注冊時有時會要求填多個郵箱,這個<input type=”text” name=”email”>就可以重複多行用同一個name,它在Servlet端擷取值方式跟checkbox一樣處理。
鑒于各種原因,我們在JavaScript擷取HTML Form表單域方法,決定采用getElementsByName方法,這樣,我們是不是每取一個表單元素,就得将NodeList類型的傳回結果周遊一遍,擷取Node,再從Node擷取值呢?既然選擇了這樣做,當然少不了這樣的工作,不過,我們可以寫一個可重用的方法,讓它處理一下NodeList對象。且看代碼:
Js代碼

- function nodeList2ValuesArray(nodeList) {
- //結果值數組,形如:['aaa','bbb','ccc']
- var values = new Array();
- var length = nodeList.length;
- var nodeName = nodeList.item(0).nodeName;
- //對"<input type='xxx'/>"的處理
- if(nodeName == "INPUT") {
- var type = nodeList.item(0).getAttribute("type");
- if(type == "text" || type == "password" || type == "hidden") {
- for(var i = 0 ; i < length ; i ++ ) {
- values.push(nodeList.item(i).value);
- }
- }
- else if(type == "checkbox" || type == "radio") {
- for(var i = 0 ; i < length ; i ++ ) {
- var node = nodeList.item(i);
- if(node.checked) {
- values.push(node.value);
- }
- }
- }
- }
- //對"<textarea>xxx</textarea>"的處理
- else if(nodeName == "TEXTAREA") {
- for(var i = 0 ; i < length ; i ++ ) {
- values.push(nodeList.item(i).value);
- }
- }
- //對"<select></select>"的處理
- else if (nodeName == "SELECT") {
- var subNodeList = nodeList.item(0).getElementsByTagName("option");
- return nodeList2ValuesArray(subNodeList);
- }
- //對<select>的子元素"<option>xxx<option>"的處理
- else if (nodeName == "OPTION") {
- for(var i = 0 ; i < length ; i ++ ) {
- var node = nodeList.item(i);
- if(node.selected){
- values.push(node.value);
- }
- }
- }
- return values;
- }
上面代碼将NodeList轉成了值數組,而且讓使用者忽略表單域類型的差異,專心于值的擷取和使用。美中不足的是,上面的方法傳回的是數組,我們還是一個一個周遊再拼成key=value字元串。接下來的方法不但解決了這一點,而且還大大簡化我們的表單取值,且看:
Js代碼

- function parseForm2ParamMap() {
- var map = new ParamMap();
- //參數的長度,注意:方法定義的括号中雖然沒有定義參數,但卻可以設任意多個參數
- var length = arguments.length;
- for(var i = 0 ; i < length ; i ++ ) {
- var nodeList = document.getElementsByName(arguments[i]);
- var values = nodeList2ValuesArray(nodeList);
- for(var j = 0 ; j < values.length ; j ++ ) {
- map.put(arguments[i], values[j]);
- }
- }
- return map;
- }
這個方法傳回值的類型是ParamMap,也就是我們剛才定義的用來傳遞參數的類,這個類中有一個toString的方法,不就是傳回“key1=value1&key2=value2”形式的字元串嗎?
OK,下面的測試例子,将涉及到表單的各種元素域,其中引進了ajax.js檔案,其内容即是我們上面所寫的JS函數和類,在附件中可下載下傳到,在此就不重複貼出來了。且看HTML代碼:
Html代碼

- <?xml version="1.0" encoding="UTF-8" ?>
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- <title>測試Ajax與Servlet之間的資料傳遞</title>
- <script type="text/javascript" src="js/ajax.js"></script>
- <script type="text/javascript">
- window.onload = function(){
- var submit = document.getElementById("submit");
- submit.onclick = function(){
- var map = parseForm2ParamMap("username", "email", "password", "userid", "sex", "framework", "salary", "dream");
- //doPost("input", map.toString(), callback); //預設Alert出資訊
- doPost("input", map.toString(), function(){
- if(xmlHttp.readyState == 4) {
- if(xmlHttp.status == 200) {
- var returnMsg = document.getElementById("returnMsg");
- returnMsg.innerHTML = xmlHttp.responseText;
- }
- }
- });
- };
- };
- </script>
- </head>
- <body>
- <h2>測試Ajax與Servlet之間的資料傳遞<br />(Step1:從Client->Server)</h2>
- <div id="returnMsg" style="color:red;"></div>
- <form>
- <!-- 文本域 -->
- <p>你的姓名:
- <input type="text" name="username" /><br />
- </p>
- <p>你的電子郵件(可填兩個):<br />
- 郵件位址1:<input type="text" name="email" /> <br />
- 郵件位址2:<input type="text" name="email" />
- </p>
- <!-- 密碼域 -->
- <p>你的密碼:
- <input type="password" name="password" /><br />
- </p>
- <!-- 隐藏域 -->
- <input type="hidden" name="userid" value="108" /><br />
- <!-- 單選框 -->
- <p>你的性别 :
- <label><input type="radio" name="sex" value="male" />男</label>
- <label><input type="radio" name="sex" value="female" />女</label>
- </p>
- <!-- 複選框 -->
- <p>你掌握的開源架構 :<br />
- <label><input type="checkbox" name="framework" value="struts" />Struts</label>
- <label><input type="checkbox" name="framework" value="spring" />Spring</label>
- <label><input type="checkbox" name="framework" value="hibernate" />Hibernate</label>
- <label><input type="checkbox" name="framework" value="ibatis" />iBatis</label>
- <label><input type="checkbox" name="framework" value="webwork" />WebWork</label>
- </p>
- <!-- 下拉清單 -->
- <p>你的薪水範圍 :
- <select name="salary">
- <option value="2000以下">2000以下</option>
- <option value="2000——3000">2000——3000</option>
- <option value="3000——4000">3000——4000</option>
- <option value="4000——5000">4000——5000</option>
- <option value="5000以上">5000以上</option>
- </select>
- </p>
- <!-- 文本域 -->
- <p>你的職業夢想: <br />
- <textarea rows="5" cols="36" name="dream"></textarea><br />
- </p>
- <input type="button" id="submit" value="submit" />
- </form>
- </body>
- </html>
伺服器端的代碼如下:
Java代碼

- package simple.rongxh.servlet;
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- public class Input extends HttpServlet {
- private static final long serialVersionUID = 1L;
- private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- //擷取資料
- request.setCharacterEncoding("UTF-8");
- String username = request.getParameter("username");
- String[] emails = request.getParameterValues("email");
- String password = request.getParameter("password");
- String userid = request.getParameter("userid");
- String sex = request.getParameter("sex");
- String[] framework = request.getParameterValues("framework");
- String salary = request.getParameter("salary");
- String dream = request.getParameter("dream");
- //響應給用戶端
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter out = response.getWriter();
- out.append("恭喜你,儲存成功,你的基本資訊如下:<br/>");
- out.append("姓名:" + username + "<br/>");
- for(int i = 0 ; i < emails.length ; i ++ ) {
- out.append("郵箱" + (i+1) + ":" + emails[i] + "<br/>");
- }
- out.append("密碼:" + password + "<br/>");
- out.append("編号:" + userid + "<br/>");
- out.append("性别:" + sex + "<br/>");
- out.append("架構:");
- for(int i = 0 ; framework != null && i < framework.length ; i ++ ){
- out.append(framework[i] + " ");
- }
- out.append("<br/>");
- out.append("薪水:" + salary + "<br/>");
- out.append("夢想:" + dream + "<br/>");
- out.println();
- }
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- process(request, response);
- }
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- process(request, response);
- }
- }
運作結果截圖如下:
本測試例子eclipse項目源碼下載下傳(附件中):rongxh2010_ajaxservlet。
當Java遇上Ajax,本文隻是第一篇,歡迎提出寶貴的意見和建議,我将十分感謝!