探讨Ajax获取表单值向Servlet传递的设计方案

原文地址: 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代码   收藏代码
  1. /**   
  2. * Get方式向服务器端异步发送数据   
  3. * @param url 服务器端的路径,数据发送的目的地   
  4. * @param data 发送的数据,格式如: "key1=value1&key2=value2"   
  5. * @param callback 回调函数,   
  6. */     
  7. function doGet(url, data, callback) {     
  8.     var url = url;     
  9.     if(url.indexOf("?") == -1) {     
  10.         url = url + "?" + data;     
  11.     } else {     
  12.         url = url + "&" + data;     
  13.     }     
  14.     initXmlHttp();     
  15.     xmlHttp.onreadystatechange = callback;    //注册回调函数     
  16.     xmlHttp.open("GET", url, true);        //设置连接信息     
  17.     xmlHttp.send(null);     
  18. }      
  19.     
  20. /**   
  21. * Post方式向服务器端异步发送数据   
  22. * @param url 服务器端的路径,数据发送的目的地   
  23. * @param data 发送的数据,格式如: "key1=value1&key2=value2"   
  24. * @param callback 回调函数   
  25. * @return   
  26. */     
  27. function doPost(url, data, callback) {     
  28.     initXmlHttp();        //初始化     
  29.     xmlHttp.onreadystatechange = callback;    //注册回调函数     
  30.     xmlHttp.open("POST", url, true);     
  31.     xmlHttp.setRequestHeader("Content-Type""application/x-www-form-urlencoded");     
  32.     xmlHttp.send(data);     
  33. }      
  34.     
  35. /**   
  36. * 默认回调函数   
  37. * 只在测试时使用,在doGet和doPost函数中的第三个参数callback,可由用户自定义回调函数,   
  38. * 若不设定,则调用默认的回调函数   
  39. */     
  40. function callback() {     
  41.     //判断对象的状态是否交互完成     
  42.     if(xmlHttp.readyState == 4) {     
  43.         //判断http的交互是否成功     
  44.         if(xmlHttp.status == 200) {     
  45.             //获取服务器返回的纯文本数据     
  46.             var responseText = xmlHttp.responseText;     
  47.             //获取服务器返回的XML格式数据     
  48.             //var responseXml = xmlHttp.responseXML;     
  49.             //Alert从服务器端返回的信息     
  50.             window.alert(responseText);     
  51.         }     
  52.     }     
  53. }     

  

 

对上面的代码,在这里解析一下: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代码   收藏代码
  1. /**   
  2. * Map类   
  3. * 实现了类似于Java语言中的Map接口的常用方法   
  4. */     
  5. function Map(){     
  6.     //key集     
  7.     this.keys = new Array();     
  8.     //value集     
  9.     this.values = new Array();         
  10.     //添加key-value进Map     
  11.     this.put = function(key, value){     
  12.         if(key == null || key == undefined)     
  13.             return;     
  14.         var length = this.size();     
  15.         for(var i = 0 ; i < length ; i ++ ) {     
  16.             //如果keys数组中有相同的记录,则不覆盖原记录的值   
  17.             if(this.keys[i] == key)     
  18.                 this.values[i] = value;    
  19.         }     
  20.         this.keys.push(key);     
  21.         this.values.push(value);     
  22.     };     
  23.     //获取指定key的value     
  24.     this.get = function(key){     
  25.         var length = this.size();     
  26.         for(var i = 0 ; i < length ; i ++ ) {     
  27.             if(this.keys[i] == key) {     
  28.                 return this.values[i];     
  29.             } else {     
  30.                 continue;     
  31.             }     
  32.             return null;     
  33.         }     
  34.     };      
  35.     
  36.     //移除指定key所对应的map     
  37.     this.remove = function(key) {     
  38.         var length = this.size();     
  39.         for(var i = 0 ; i < length ; i ++ ) {     
  40.             if(this.keys[i] == key) {     
  41.                 while(i < length - 1) {     
  42.                     this.keys[i] = this.keys[i+1];     
  43.                     this.values[i] = this.values[i+1];     
  44.                     i ++ ;     
  45.                 }     
  46.                 //处理最后一个元素     
  47.                 this.keys.pop();     
  48.                 this.values.pop();     
  49.                 break;     
  50.             }     
  51.         }     
  52.     };     
  53.     //是否包含指定的key     
  54.     this.containsKey = function(key) {     
  55.         var length = this.size();     
  56.         for(var i = 0 ; i < length ; i ++ ) {     
  57.             if(this.keys[i] == key) {     
  58.                 return true;     
  59.             }     
  60.         }     
  61.         return false;     
  62.     };     
  63.     //是否包含指定的value     
  64.     this.containsValue = function(value) {     
  65.         var length = this.size();     
  66.         for(var i = 0 ; i < length ; i ++ ) {     
  67.             if(this.values[i] == value) {     
  68.                 return true;     
  69.             }     
  70.         }     
  71.         return false;     
  72.     };     
  73.     //包含记录总数     
  74.     this.size = function() {     
  75.         return this.keys.length;     
  76.     };     
  77.     //是否为空     
  78.     this.isEmpty = function() {     
  79.         return this.size() == 0 ? true : false;     
  80.     };     
  81.     //清空Map     
  82.     this.clear = function() {     
  83.         this.keys = new Array();     
  84.         this.values = new Array();     
  85.     };     
  86.     //将map转成字符串,格式:key1=value1,key2=value2     
  87.     this.toString = function() {     
  88.         var length = this.size();     
  89.         var str = "";     
  90.         for(var i = 0 ; i < length ; i ++ ) {     
  91.             str = str + this.keys[i] + "=" + this.values[i];     
  92.             if(i != length-1)     
  93.                 str += ",";     
  94.         }     
  95.         return str;     
  96.     };     
  97. }     

  

代码比较长,有些方法在本例中可能用不到,但也写出来了,或者在其他地方可能有用吧。当我们使用这个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代码   收藏代码
  1. /**   
  2.  * ParamMap类,用于存储HTTP请求中的Get方法或者Post方法所传递的参数   
  3.  * 继承于Map类,但改写一些方法,以适合HTTP请求中的参数格式   
  4.  * 与Map不同之处有:ParamMap允许多个同名的"key"存在,   
  5.  * toString方法返回的"key=value"对以"&"号连接,而不是","号,等等。   
  6.  */     
  7. function ParamMap() {     
  8.     //继承Map类     
  9.     Map.call(this);     
  10.     //重写put方法,允许多个同名key存在     
  11.     this.put = function(key, value){     
  12.         if(key == null || key == undefined)      
  13.             return;     
  14.         this.keys.push(key);     
  15.         this.values.push(value);     
  16.     };     
  17.     //重写get方法,返回values数组     
  18.     this.get = function(key) {     
  19.         var results = new Array();     
  20.         var length = this.size();     
  21.         for(var i = 0 ; i < length ; i ++ ) {     
  22.             if(this.keys[i] == key)     
  23.                 results.push(this.values[i]);     
  24.         }     
  25.         return results;     
  26.     };     
  27.     //重写remove方法     
  28.     this.remove = function(key) {     
  29.         var length = this.size();     
  30.         for(var i = 0 ; i < length ; i ++ ) {     
  31.             if(this.keys[i] == key) {     
  32.                 while(i < length - 1) {     
  33.                     this.keys[i] = this.keys[i+1];     
  34.                     this.values[i] = this.values[i+1];     
  35.                     i ++ ;     
  36.                 }     
  37.                 //处理最后一个元素     
  38.                 this.keys.pop();     
  39.                 this.values.pop();     
  40.             }     
  41.         }     
  42.     };     
  43.     //重写toString方法, 转成XMLHttpRequest.send(ajaxString)方法的参数格式的字符串,     
  44.     //形如:key1=value1&key2=value2     
  45.     this.toString = function() {     
  46.         var length = this.size();     
  47.         var str = "";     
  48.         for(var i = 0 ; i < length ; i ++ ) {     
  49.             str = str + this.keys[i] + "=" + this.values[i];     
  50.             if(i != length-1)     
  51.                 str += "&";     
  52.         }     
  53.         return str;     
  54.     };     
  55. }     

 怎么使用这个ParamMap类呢,且看下面的示例代码:

Js代码   收藏代码
  1. var username = document.getElementById("username").value;     
  2. var password = document.getElementById("password").value;     
  3. var sex = document.getElementById("sex").value;      
  4.     
  5. var map = new ParamMap();     
  6. map.put("username", username);     
  7. map.put("password", password);     
  8. map.put("sex", sex);      
  9.     
  10. doGet("test/register", map.toString(), callback);     
  11. doPost("test/register", map.toString(), callback);  
Js代码   收藏代码
  1.   

 

 

 在JavaScript中,用来获取HTML结点的方法,常用的有如下方法:

Js代码   收藏代码
  1. Node  document.getElementById("username")  //根据标签的id     
  2. NodeList  document.getElementsByName(“username”)  //根据标签的name     
  3. 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代码   收藏代码
  1. String  HttpServletRequest.getParameter("keyName");  //用于单值表单域     
  2. String[]  HttpServletRequest.getParameterValues("keyName"); //用于多值表单域     

 

 

所谓的“单值表单域”就是上面提到过的type为text、password、hidden的input或者textarea等,而“多值表单域”是指checkbox。其实,也不尽然,如单值表单域有时候也可以是多值表单域,如我们注册时有时会要求填多个邮箱,这个就可以重复多行用同一个name,它在Servlet端获取值方式跟checkbox一样处理。

鉴于各种原因,我们在JavaScript获取HTML Form表单域方法,决定采用getElementsByName方法,这样,我们是不是每取一个表单元素,就得将NodeList类型的返回结果遍历一遍,获取Node,再从Node获取值呢?既然选择了这样做,当然少不了这样的工作,不过,我们可以写一个可重用的方法,让它处理一下NodeList对象。且看代码:

 

Js代码   收藏代码
  1. /**   
  2.  * 获取nodeList的值集   
  3.  * @param nodeList Node数组,可通过getElementsByName或者getElementsByTagName等方法获得   
  4.  * @return 值数组   
  5.  */     
  6. function nodeList2ValuesArray(nodeList) {     
  7.     //结果值数组,形如:['aaa','bbb','ccc']     
  8.     var values = new Array();     
  9.     var length = nodeList.length;     
  10.     var nodeName = nodeList.item(0).nodeName;     
  11.     //对""的处理     
  12.     if(nodeName == "INPUT") {     
  13.         var type = nodeList.item(0).getAttribute("type");     
  14.         if(type == "text" || type == "password" || type == "hidden") {     
  15.             for(var i = 0 ; i < length ; i ++ ) {     
  16.                 values.push(nodeList.item(i).value);     
  17.             }     
  18.         }     
  19.         else if(type == "checkbox" || type == "radio") {     
  20.             for(var i = 0 ; i < length ; i ++ ) {     
  21.                 var node = nodeList.item(i);     
  22.                 if(node.checked) {     
  23.                     values.push(node.value);     
  24.                 }     
  25.             }     
  26.         }     
  27.     }     
  28.     //对""的处理     
  29.     else if(nodeName == "TEXTAREA") {     
  30.         for(var i = 0 ; i < length ; i ++ ) {     
  31.             values.push(nodeList.item(i).value);     
  32.         }     
  33.     }     
  34.     //对""的处理     
  35.     else if (nodeName == "SELECT") {     
  36.         var subNodeList = nodeList.item(0).getElementsByTagName("option");     
  37.         return nodeList2ValuesArray(subNodeList);     
  38.     }     
  39.     //对