jQuery的使用(二)

这篇文章我们主要想介绍的是 Ajax,也就是一种浏览器向服务器发送异步请求的技术,在之前的文章中,其实所有的对于后台的请求操作都是同步请求,即当浏览器向服务器发送一个请求之后,那么就必须等待服务器有所响应之后,浏览器端才能再进行下一次请求,而且同步请求每次更新都是更新的整个页面,而异步请求就不一样了,每次请求更新的可以只是页面的局部内容,而且在浏览器端向后台发送一次请求之后,浏览器端也无需等待,可以接着执行其它操作,比如再发送一次异步请求,这样来看的话,使用异步请求可以使用户的体验更好,因为没有了同步请求时所需要的等待,因此,在开发过程中使用 Ajax 异步请求,可以使我们的网站更加友好。

1.Ajax介绍

Ajax 其实是 Asynchronous Javascript And XML 的缩写,也就是异步的 JavaScriptXML,其实 Ajax 并不是一种新的技术,而是之前已经出现技术的组合,流行起来的原因也是由于 Google 的相关应用,如 Google 地图和搜索。Ajax 最大的优点就在于可以实现页面的局部刷新,当然这就是因为可以使用它向后台发送异步请求了,因此就不必像同步请求那样,每次向后台发送请求之后,都必须等待后台有所响应之后才能进行别的操作,而且同步请求每次更新时都是更新的整个页面。

现在的浏览器中都包含有 Ajax 引擎,而 Ajax 引擎中核心对象就是 JavaScript 对象 XMLHttpRequest 了,使用这个对象,我们就能向后台发送异步请求了。其实当我们在前端页面中发送一次异步请求时,就是使用的 JavaScript 代码调用 XMLHttpRequest 向后台发送一次 HttpRequest 请求,等到有数据响应回 Ajax 引擎时,就可以再调用 JavaScript 代码解析返回的数据并显示在页面中,这样就是一次完整的 Ajax 异步请求过程,这样的请求最大的优点就是当页面向 Ajax 引擎发送一次请求之后,不必等待服务器端数据的响应就可以再发送一次请求,这样的话用户在使用时就不会感到长时间的等待了。

上面也说到了 Ajax 引擎中核心的对象就是 XMLHttpRequest 对象了,因此下面我们会详细地介绍该对象的使用,并详细地说明该对象的 api 方法以及属性,当然后面我们也会介绍使用 jQuery 来发送 Ajax 异步请求,其实使用 jQuery 的话就是相当于对底层使用 XMLHttpRequest 发送请求进行封装,当然封装了之后我们使用起来就会非常简单,其实我们以后开发过程中使用的最多的肯定就是 jQuery 了,不过我们应该不仅要掌握使用 jQuery 来发送异步请求,而且也应该掌握它底层的 XMLHttpRequest 对象是如何工作的,这样的话我们就能把知识理解的更加透彻。

2.XMLHttpRequest对象的使用

这里想要说明的就是 XMLHttpRequest 对象的基本使用,其实在 w3cschool 网站中对于这个对象有很详细的说明,而且还有具体的例子进行了演示,我们可以参照该网站进行学习。

当然我们的重点还是在于使用 XMLHttpRequest 对象来向后台发送异步请求,因此可以设置一个场景,比如在前端页面中有一个按钮,当我们点击按钮时,就会向后台发送一次 Ajax 异步请求,当后台对于我们的请求有所响应时,我们也应该在前台页面处理后端返回的数据,那我们处理的思路应该是怎样的呢?其实是可以如下的:

1.获得XMLHttpRequest对象;
2.设置回调函数;回调函数其实就是用来处理后台返回的数据的
3.设置请求方式以及请求路径;
4.向服务器端发送请求;
5.在回调函数中处理服务器端响应的数据。

其实上面的步骤也就是我们开发时常用的步骤,首先我们看如何获取 XMLHttpRequest 对象,其实在 w3cschool 网站中介绍这个对象的时候,就给了很清晰的获取代码,如下所示:

// 1.获取XMLHttpRequest对象
if (window.XMLHttpRequest) {
    // code for all new browsers
    xmlhttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
    // code for IE5 and IE6
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

上面这段代码便可以兼容新旧的浏览器了,从而可以得到 XMLHttpRequest 对象,获取到核心对象之后,我们便需要设置回调函数,以便于用来处理后台响应回来的数据,当然这个是通过核心对象的 onreadystatechange 属性来设置的:

// 2.设置回调函数
xmlhttp.onreadystatechange = callback;

上面代码中的 callback 其实是一个函数名,也就是我们处理响应数据的回调函数的名称,因此我们还需要在下面写一个名为 callback 的函数,用来接收响应数据并进行处理,关于响应函数的说明我们还是稍后再进行展示,下面再看设置请求方式和请求路径。

// 3.设置请求方式和url
var url = "/ajax/ajaxServlet";
xmlhttp.open("GET", url, true);

设置请求方式和请求路径的话是使用的核心对象的 open() 方法,其中的 url 便是请求路径,我们这里是使用的一个 Servlet 来接收请求的,而在 url 中的 ajax 便是我们工程的名称,ajaxServlet 便是 Servlet 的请求路径,关于请求方式的话,我们平时使用的最多的就是 postget 这两种了,在这里的话可以看到,我们是使用的 get 方式。

下面的话就是发送真正的请求了,就可以直接使用核心对象的 send() 方法,这样就是真正地发送了一次异步请求,

// 4.发送请求
xmlhttp.send(null);

因为我们这次的操作只是点击页面中的一个按钮,然后向后台发送一次请求,其实是不需要往后台传送什么数据的,因此可以在 send() 方法中直接写一个 null

最后的话就是看后台返回响应数据,然后我们在前端的回调函数中处理响应数据,我们在后台是使用的 Servlet 来接收请求的,这里的话Servlet 是没有做特殊的逻辑处理的,只是使用 response 对象简单的返回了一个字符串:

response.getWriter().write("You are right!!!");

所以后台的处理是非常简单的,最后的话就是要看我们在前端的回调函数中处理响应数据了,在之前设置回调函数的时候,我们就提到过,我们回调函数的名称为 callback ,因此我们需要写一个 callback() 函数来处理响应数据。

function callback() {
    if (xmlhttp.readyState == 4) {// 4 = "loaded"
        if (xmlhttp.status == 200) {// 200 = OK
            var text = xmlhttp.responseText;
            alert(text);
        }
    }
}

其实回调函数中的处理也非常简单,就是使用核心函数的 responseText 属性获取到后台返回的响应数据,然后在浏览器中进行弹出显示。这样的话,我们就使用 XMLHttpRequest 对象完成了一次异步请求,完整的代码如下:首先是页面中需要一个按钮

<input type="button" value="按钮" onclick="sendAjax()">

然后就是该按钮所对应的点击事件了,主要便是 JavaScript 代码的编写了。

<script type="text/javascript">
    var xmlhttp = null;
    function sendAjax() {
        // 1.获取XMLHttpRequest对象
        if (window.XMLHttpRequest) {// code for all new browsers
            xmlhttp = new XMLHttpRequest();
        } else if (window.ActiveXObject) {// code for IE5 and IE6
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }

        // 2.设置回调函数
        xmlhttp.onreadystatechange = callback;

        // 3.设置请求方式和url
        var url = "/ajax/ajaxServlet";
        xmlhttp.open("GET", url, true);

        // 4.发送请求
        xmlhttp.send(null);

    }

    function callback() {
        alert(xmlhttp.readyState);
        if (xmlhttp.readyState == 4) {// 4 = "loaded"
            if (xmlhttp.status == 200) {// 200 = OK
                var text = xmlhttp.responseText;
                alert(text);
            }
        } else {
            alert("Problem retrieving XML data");
        }
    }
</script>

这样的话,我们就学会如何使用 XMLHttpRequest 对象了,关于该对象的相关属性和方法,下面我们再进行详细介绍。

3.XMLHttpRequest对象的属性和方法

下面我们就对 XMLHttpRequest 对象常用的属性和方法进行介绍,首先看如何获取 XMLHttpRequest 对象,对于现代浏览器来说,只需要使用下面这行代码就可以获取得到:

xmlhttp=new XMLHttpRequest();

而旧的浏览器的话,则需要使用下面这行代码来进行获取:

xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");

3.1 XMLHttpRequest对象常用属性

1.onreadystatechange属性

该属性用于绑定回调函数,当我们向服务器端发送请求,而服务器端接受请求并处理以及响应回数据的时候,我们便可以在回调函数中处理服务器端响应回来的数据了。

2.readyState属性

该属性表示 HTTP 的请求状态,一共是分为 0,1,2,3,45 个状态,分别是对应着 XMLHttpRequest 对象创建时的 0 一直到接受到完整的 HTTP 响应的状态 4,完整的描述可以看下面的说明。

状态        名称                描述
0        Uninitialized    初始化状态。XMLHttpRequest对象已创建或已被 abort() 方法重置。
1        Open            open() 方法已调用,但是send()方法未调用,请求还没有被发送。
2        Send            send() 方法已调用,HTTP请求已发送到 Web 服务器,但未接收到响应。
3        Receiving        所有响应头部都已经接收到,响应体开始接收但未完成。
4        Loaded            HTTP 响应已经完全接收。

其实仔细看上面各个状态的描述的话,就可以发现上面状态对应的就是一次完整请求所经过的各个阶段,记住了一次完整请求所要经历的几个重要步骤,那么上面几个状态也就好理解了。

3.status属性

该属性表示的是 HTTP 的状态代码,比较常见的有 200,404,500 这些,200 表示的则是成功,404 表示的则是未找到,500 则是表示内部服务器错误。

4.responseText和responseXML属性

这两个属性则是表示服务器端返回的响应数据,responseText 属性则是表示返回的数据类型是普通文本类型,而 responseXML 属性则是表示返回的数据类型为 XML

3.2 XMLHttpRequest对象常用方法

1.open()方法

该方法主要是用来设置请求方式、url 以及是否异步的,第 1 个参数就是请求方式,一般我们常用的就是 postget 这两种了,第 2 个参数则是我们要访问的 url 路径,第 3 个参数则是表示请求是否是异步的,取值为 true/false,为 true 则表示是异步的,false 则表示为同步的,默认情况下则是为异步的。

2.send()方法

该方法就是向服务器发送请求了,这个方法只有 1 个参数,那就是请求体,因为 get 请求方式是没有请求体的,因此当我们使用 get 方式向服务器发送请求时,就可以直接将该方法的参数设置为 null,而使用 post 请求方式时,就可以在方法参数中设置需要的请求体了。

3.setRequestHeader()方法

这个方法就是用来设置请求头的,需要注意的一点就是,在我们使用 post 方式向服务器端发送异步请求时,一定要设置一个请求头,那就是:

xmlhttp.setRequestHeader("content-type","application/x-www-form-urlencoded");

4.验证用户名是否可以使用

上面我们介绍了 XMLHttpRequest 对象的基本使用和常用属性以及方法,下面我们就可以使用 XMLHttpRequest 对象来做一些小的案例,比如通过 XMLHttpRequest 对象来验证我们用户名输入框中输入的用户名是否已经被占用了,并给出响应的提示信息。

要完成上面的例子其实还是很简单的,首先当用户在输入框中输入用户名并失焦的时候,我们获取得到输入框中输入的用户名并向后台发送一次 Ajax 请求,这样我们就可以在后台验证该用户名是否已经被占用了,当然我们这里为了方便,就不去查询数据库了,而是直接判断用户名是否等于某个特定的值了,最后将判断结果返回到前台,前端解析后台返回的数据,并将相关信息展示在页面中。

首先我们前端页面中需要一个输入框,然后输入框后面可以紧跟一个展示返回信息的 span 元素,用来展示服务器返回的响应信息,如下所示:

用户名:<input type="text" id="user" onblur="checkUserName()">
<span id="msg"></span>

可以看到,onblur 属性则是为元素绑定了失焦事件,当元素失去焦点时便会触发该事件,其实我们想要做的就是当输入框失去焦点时能够获取得到输入框中的值,并带着该值向后台发送一次异步请求,也就是我们 checkUserName() 方法中需要做的了。

当我们使用 XMLHttpRequest 对象发送异步请求时,一定是先获取到该对象,这样的话我们就可以将获取该对象抽取成一个方法,先创建一个 my.js 文件,然后在该文件中写抽取的方法。

function getXmlHttpRequest() {
    var xmlhttp = null;
    // 1.获取XMLHttpRequest对象
    if (window.XMLHttpRequest) {// code for all new browsers
        xmlhttp = new XMLHttpRequest();
    } else if (window.ActiveXObject) {// code for IE5 and IE6
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    return xmlhttp;
}

这样我们每次想要使用该方法时,只需要在页面中引入 my.js 文件就可以直接使用了,引用方法如下:

<script type="text/javascript" src="./js/my.js"></script>

src 属性所对应的属性值便是 my.js 文件相对于当前页面来说的相对路径了,引入之后我们便可以在当前页面直接调用 getXmlHttpRequest() 方法获取得到 XMLHttpRequest 对象了。

我们现在可以获取 XMLHttpRequest 对象之后,那接下来需要做的就是获取输入框中输入的用户名,然后将该用户名发送到后台以作验证,首先是获取到输入框中的值:

var userName = document.getElementById("user").value;

下面那就是使用 XMLHttpRequest 对象发送异步请求了,这里设置回调函数的时候我们就直接声明一个函数了,而不在外面再进行定义了。

var xmlhttp = getXmlHttpRequest();
xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {// 4 = "loaded"
        if (xmlhttp.status == 200) {
            // 根据服务器响应回的信息,在页面中进行显示
            var msg = xmlhttp.responseText;
            document.getElementById("msg").innerHTML = msg;
        }
    }
};

最后就是设置请求方式以及请求路径的一些操作了,直接看代码就能理解了。

xmlhttp.open("POST", "/ajax/userServlet");
// 发送请求,post发送需要设置content-type请求头
xmlhttp.setRequestHeader("content-type","application/x-www-form-urlencoded");
xmlhttp.send("userName=" + userName);

open() 方法是设置了请求方式和请求路径,这里是采用的 post 请求方式,请求路径就是 /ajax/userServlet 了,因为是使用的 post 请求方式,因此需要设置一个请求头,那就是 content-type,最后我们使用 send() 方法正式发送请求时,请求体中便带上了 userName 参数,这样在后台也就可以获取到该参数以及对应的参数值了。

前端的代码其实还是非常简单的,主要是输入框失焦时触发的函数 checkUserName() 完成了主要功能,下面可以看看完整的代码:

function checkUserName() {
    // 1.得到文本框中的值
    var userName = document.getElementById("user").value;
    // alert(userName);
    // 2.使用ajax向服务器发送请求,并携带userName
    // 2.1 获取XMLHttpRequest
    var xmlhttp = getXmlHttpRequest();
    // 2.2 设置回调函数
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {// 4 = "loaded"
            if (xmlhttp.status == 200) {
                // 3.根据服务器响应回的信息,在页面中进行显示
                var msg = xmlhttp.responseText;
                document.getElementById("msg").innerHTML = msg;
            }
        }
    };

    // 2.3 设置请求方式和url
    xmlhttp.open("POST", "/ajax/userServlet");
    // 2.4 发送请求,post发送需要设置content-type请求头
    xmlhttp.setRequestHeader("content-type",
            "application/x-www-form-urlencoded");
    xmlhttp.send("userName=" + userName);
}

前端的代码就介绍完了,我们再来看后端的代码,我们这里是使用的 UserServlet 来处理的前端请求,其实也是非常简单的:

String userName = request.getParameter("userName");
String msg = "";
if ("tom".equals(userName)) {
    msg = "<font color='red'>用户名已经被占用</font>";
} else {
    msg = "<font color='green'>用户名可以使用</font>";
}
// 解决中文响应乱码
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write(msg);

这样的话,我们就处理了前端页面过来的请求,并根据前端传过来的用户名进行处理返回了相应的数据。最后我们只需要在前端回调函数中处理后台返回的数据就好了,当然我们这里只需要将后台返回的数据插入到输入框后面的 span 元素就好了,代码如下:

var msg = xmlhttp.responseText;
document.getElementById("msg").innerHTML = msg;

这样的话我们就完成了验证用户名是否可用的例子了,其实最关键的还是要掌握其中具体的步骤,理解了每一步需要做什么也就能很好的完成该例子了。

5.使用XMLHttpRequest对象完成省市二级联动

下面我们再使用 XMLHttpRequest 对象完成一个省市二级联动的例子,其实就是有两个下拉选择框,第一个下拉选择框展示省份信息,第二个下拉选择框展示城市信息,当页面加载完成时,我们会向后台发送一次请求,以便得到省份信息,并展示在第一个下拉选择框当中,而当我们选择第一个下拉选择框中的省份时,也会向后台发送一次请求,以便得到选择省份所对应的城市信息,并将城市信息展示在第二个下拉选择框当中,这就是我们需要完成的功能。

首先我们看前端的页面元素,当然是需要有两个下拉选择框了,用于展示省份和城市信息,然后就是还需要设置页面加载完成事件以及选择省份时触发对相应城市信息的请求,页面加载完成事件可以通过 body 元素的 onload 属性来设置,而选择省份时触发对城市信息的请求则可以在第一个下拉选择框元素中设置 onchange 属性来完成,具体的代码如下:

<body onload="getProvince()">
    <select id="province" onchange="getCity()">
        <option>--请选择省份--</option>
    </select>
    <select id="city">
        <option>--请选择城市--</option>
    </select>
</body>

因为我们省份和城市的信息都是向后台请求得到的,因此我们先看后台的数据是怎么组织的,这里为了方便,就不去数据库进行查询操作了,而是直接硬编码在工具类里面了,并且在工具类中我们也可以提供获取省份信息以及根据省份名称获取相应城市信息的方法,这样也方便我们获取数据,下面便是工具类 (CityUtil) 的具体代码了。

public class CityUtil {
    private static Map<String, String> citys = new HashMap<>();
    static {
        citys.put("湖北", "武汉,天门");
        citys.put("四川", "成都,都江堰");
    }

    public static String getProvince() {
        Set<String> keySet = citys.keySet();
        String province = "";
        for (String key : keySet) {
            province += key + ",";
        }
        return province.substring(0, province.length() - 1);
    }

    public static String getCitys(String province) {
        return citys.get(province);
    }
}

有了封装省份和城市信息的工具类之后,我们在后台编码时就可以很容易地获取到相关数据了,而我们在后台接收前端请求时,是使用的 Servlet 进行接收的,对于省份信息的请求我们是使用的 ProvinceServlet,对于城市信息的请求我们是使用的 CityServlet,这样就能很清晰地处理对于省份信息和城市信息的请求了,下面我们来看这两个 Servlet 是如何来处理相应的请求的,先看 ProvinceServlet,因为对省份信息的请求,前端页面是不需要携带什么请求参数的,因此我们只需要把所有的省份信息返回就好了:

String province = CityUtil.getProvince();
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write(province);

这样的话我们就可以完成前端页面对于省份信息的请求了,下面再看 CityServlet 如何处理前端页面对于城市信息的请求,获取城市信息的话我们需要知道是获取哪个省份的,因此需要前端页面发送请求时携带着省份信息,这样就能根据省份信息获取到对应的城市信息。

// 防止中文乱码
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());

String province = request.getParameter("province");
String citys = CityUtil.getCitys(province);
response.getWriter().write(citys);

这样的话前端页面对于城市信息的请求后台部分也是可以处理的了,所以后台部分代码就是这样的了,那最后就是看前端发送异步请求的代码了,先看前端页面为了获取省份信息的代码:

// 获取省份信息
function getProvince() {
    // 1.创建XMLHTTPRequest请求对象
    var xmlhttp = getXmlHttpRequest();
    // 2.设置回调函数
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {// 4 = "loaded"
            if (xmlhttp.status == 200) {
                // 3.根据服务器响应回的信息,在页面中进行显示
                var province = xmlhttp.responseText;
                var pros = province.split(",");
                for (var i = 0; i < pros.length; i++) {
                    // 创建一个option对象,并赋上文本值
                    var option = document.createElement("option");
                    option.text = pros[i];
                    // 将option选项添加到select对象中
                    document.getElementById("province").add(option);
                }
            }
        }
    };
    // 3.设置请求方法和url
    xmlhttp.open("POST", "/ajax/provinceServlet");
    // 4.发送请求,post发送需要设置content-type请求头
    xmlhttp.setRequestHeader("content-type",
            "application/x-www-form-urlencoded");
    xmlhttp.send(null);
}

现在来看的话,因为之前我们对如何获取 XMLHttpRequest 对象以及如何设置请求方式和请求路径已经有了详细的介绍了,因此我们现在可以直接关注对于服务端响应回来的数据,在回调函数中应该如何处理,细心的话会发现我们之前后台代码中的工具类在返回省份信息和城市信息时,省份名称和城市名称之间都是使用逗号隔开的,因此在前端我们处理后端返回的数据时,也应该要根据逗号来分割返回的数据,这样才能得到一个一个的省份或者城市名称,其实上面的代码也就是这样做的,分割得到一个一个省份名称之后,然后再创建对应数量的 option 元素,option 元素中的文本则是省份名称,最后则将这些创建的 option 元素都添加到第一个下拉选择框当中,也就是展示省份信息的下拉选择框了。

介绍完了对于省份信息的请求以及返回数据的处理之后,我们再看对于城市信息的请求以及对于服务器端响应数据的处理了,其实处理逻辑还是和处理省份信息的相类似的。

// 获取城市信息
function getCity() {
    var proEle = document.getElementById("province");
    var opts = proEle.options;
    // 得到选中的option对象
    var option = opts[proEle.selectedIndex];
    var province = option.text;
    // 1.创建XMLHTTPRequest请求对象
    var xmlhttp = getXmlHttpRequest();
    // 2.设置回调函数
    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {// 4 = "loaded"
            if (xmlhttp.status == 200) {
                document.getElementById("city").innerHTML = "<option>--请选择城市--</option>";
                // 3.根据服务器响应回的信息,在页面中进行显示
                var city = xmlhttp.responseText;
                var citys = city.split(",");
                for (var i = 0; i < citys.length; i++) {
                    // 创建一个option对象,并赋上文本值
                    var option = document.createElement("option");
                    option.text = citys[i];
                    // 将option选项添加到select对象中
                    document.getElementById("city").add(option);
                }
            }
        }
    };
    // 3.设置请求方法和url
    xmlhttp.open("POST", "/ajax/cityServlet");
    // 4.发送请求,post发送需要设置content-type请求头
    xmlhttp.setRequestHeader("content-type",
            "application/x-www-form-urlencoded");
    xmlhttp.send("province=" + province);
}

当然处理城市信息相比较于处理省份信息的话,多出的步骤就是我们需要先知道当前选中的省份是哪一个,然后才能向后台发送请求,其余的处理也就是类似的了,也是在回调函数中处理服务器端响应回来的数据,将数据分割得到一个一个的城市名称,然后创建对应的 option 元素,option 元素中的文本就是城市名称,最后将这些 option 元素都添加到展示城市信息的下拉选择框当中,这样就完成最终的功能了,不过有一点需要注意的是,在每一次请求城市信息时,都需要将上一次请求得到的城市信息清空,也就是将展示城市信息的下拉选择框清空,这样就不会导致多次请求的城市信息进行叠加,具体代码如下:

document.getElementById("city").innerHTML = "<option>--请选择城市--</option>";

这样的话,我们就完成了省市二级联动的功能,其实只要清楚这个功能具体需要哪几个步骤,还是很容易实现这个功能的。

6.jQuery中的Ajax开发

上面我们是介绍的使用 XMLHttpRequest 进行 Ajax 开发的内容,使用过程中会发现使用 XMLHttpRequest 的话,代码量会比较多,而且很多代码都是重复的,因此我们下面就介绍使用 jQuery 进行 Ajax 开发,相当于是对 XMLHttpRequest 对象进行了封装,因此使用起来非常简单和简洁,下面先看具体有哪几种方式。

6.1 jQuery中Ajax请求的种类

jQueryAjax 的请求方式一共有以下这几种:

$.ajax(url,[settings])
load(url,[data],[callback])
$.get(url,[data],[fn],[type])
$.getJSON(url,[data],[fn])
$.getScript(url,[callback])
$.post(url,[data],[fn],[type])

其中最基本的一种方式就是 $.ajax() 这种方式了,使用这种方式的话我们会看到发送 Ajax 请求需要的各种参数,这样我们也能对发送请求有一个比较完整的了解,而 load()$.get() 以及 $.post() 这三种方式则是对 $.ajax() 方式的简化,我们使用起来会更加简单,因此在实际开发中会使用的比较多,而最后的 $.getJSON()$.getScript() 则是针对于跨域使用的,后面我们也会进行详细的介绍,这 6 种便是 jQuery 进行 Ajax 开发的主要方式了,熟练掌握的话我们就能轻松应对相关问题的解决了。

6.2 load()方式

下面我们就来看一下 jQuery 中进行 Ajax 开发的第 1 种方式,那就是 load() 方法了,使用这种方法是直接将服务器端响应回来的 HTML 数据插入到 DOM 元素中,那具体应该是插入到哪个 DOM 元素中呢?那就要看是哪个 DOM 元素调用的这个 load() 方法了。下面我们再看 load() 方法中的 3 个参数:

load(url, [data], [callback])
url:请求路径
data:前端发送到服务器端的键值对参数
callback:请求成功时的回调函数

我们还是通过一个具体的例子来看该方法,这样可以更清晰地明白该方法应该如何使用,还是验证用户名是否可以使用的例子。

前端页面中仍然是一个输入框用于输入用户名,后面我们可以放一个 span 元素用来显示之后服务器端响应回来的数据,当然,因为最终服务器端响应回来的 HTML 数据需要插入到这个 span 元素当中,所以等一下应该也是使用该元素来调用 load() 方法,代码如下:

用户名:<input type="text" id="userName"><span id="msg"></span>

接着我们再看后端使用 Servlet 处理前端请求的代码,其实和之前的代码是一样的,这里也还是放上来 (LoadServlet)

// 防止乱码
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());

String userName = request.getParameter("userName");
String msg = "";
if ("tom".equals(userName)) {
    msg = "<font color='color'>用户名已经被占用</font>";
} else {
    msg = "<font color='green'>用户名可以使用</font>";
}
response.getWriter().write(msg);

最后我们就来看在前端页面使用 load() 方法向后台发送 Ajax 请求了,先看代码:

$(function() {
    $("#userName").blur(function() {
        var userName = $("#userName").val();
        // 使用load方法发送ajax请求
        var url = "/jqueryAjax/loadServlet";
        $("#msg").load(url,{"userName":userName},function(data){
            alert(data);
        });
    });
})

可以看到,上面是由 span 元素直接调用的 load() 方法,然后服务器端响应回来的数据就会直接插入到该 span 元素中,其实这里的参数只需要 url 请求路径和 data 请求参数这两个就好了,是不需要最后一个参数 callback 回调函数的,这里只是为了看得更清楚该方法的使用,所以才加上的,加上的效果就是会在浏览器中直接弹出服务器端响应的数据。

load() 方法使用需要注意的地方,第一点就是我们上面已经说过的,使用该方法是直接将服务器端响应回来的数据插入到调用该方法的 DOM 元素当中,第二点那就是使用该方法是使用的哪种请求方式,其实也非常容易记忆,那就是没有参数时是使用的 GET 方式,而有参数时是使用的 POST 方式。

6.3 $.ajax()方式

下面我们再接着看如何使用 $.ajax() 方式进行 Ajax 请求,其实 $.ajax() 方式是最基本的一种方式了,load()$.get()$.post() 都是对这一种方式的简化,我们学习这一种方式的话,可以看到比较全的参数设置,即最基本的这种 Ajax 请求方式一般会设置哪些参数:

url:请求路径
data:前端发送到服务器端的键值对参数
type:请求方式,如GET或者POST
dataType:服务器端返回数据类型,可以为以下类型
    xml:返回XML数据
    html:返回HTML文本
    script:返回JavaScript代码
    json:返回JSON数据
    jsonp:JSONP格式,主要用于跨域操作
    text:返回纯文本字符串
success:请求成功时的回调函数

下面我们还是通过验证用户名是否可用这个例子来学习 $.ajax() 请求方式,其实需要修改的代码也就是使用 load() 方法那一句,完整的代码为:

$(function() {
    $("#userName").blur(function() {
        var userName = $("#userName").val();
        // 使用load方法发送ajax请求
        var url = "/jqueryAjax/loadServlet";
        $.ajax({
            type:"post",
            url:url,
            data:{"userName":userName},
            dataType:"html",
            success:function(data){
                $("#msg").html(data);
            }
        });
    });
})

使用 $.ajax() 不会为 DOM 元素自动插入服务器端响应的数据,因此需要在回调函数中处理服务器端响应的数据,即将返回的数据插入到指定的 DOM 元素中,别的需要注意的地方就是这种方式设置的各个参数吧,在上面也进行了说明。

6.4 $.get()和$.post()方式

下面我们再看 $.get()$.post() 请求方式,这两种方式是我们在开发中会经常使用的两种方式,因为使用起来非常简单,所以这两种方式我们都应该能熟练掌握,这两种请求方式主要是请求方式不同,它们所需要的参数设置都是一样的,如下所示:

url:请求路径
data:前端发送到服务器端的键值对参数
callback:请求成功时的回调函数
type:服务器端响应数据的格式

同样的我们也还是用上面验证用户名是否可以使用的案例来学习这两种请求方式,主要的不同当然就是发送请求时的代码了,当我们使用 post 请求方式时,代码应该为:

var url = "/jqueryAjax/loadServlet";
$.post(url,{"userName":userName},function(data){
    $("#msg").html(data);
},"html");

当使用 get 请求方式,代码则应该为:

var url = "/jqueryAjax/loadServlet";
$.get(url,{"userName":userName},function(data){
    $("#msg").html(data);
},"html");

上面两种请求方式的代码进行比较的话,就会发现,代码完全是一样的,只是请求方式不一样而已,因此我们在实际使用只要学会了其中的一种,另外的一种也就学会了,需要说明的一点是,回调函数中的 data 参数就是服务器端响应回来的数据了。

7.JSON数据

在我们上面所写的案例当中,当判断输入的用户名是否可用时,服务器端会响应相应的信息给前端,以便让前端页面进行展示,这里返回的信息只是普通的文本信息,那在实际开发中,比如我们想获取相关产品的相关信息时,普通文本还能满足我们的要求吗?很明显如果还使用普通文本的话,是满足不了相关要求的,因为可能一件商品的属性就比较多,在前端页面对普通文本信息进行解析就会非常麻烦,因此我们就要来学习另外的一种数据交换格式,也就是 JSON 了,使用了 JSON 之后,就会发现,描述相关数据可以如此简单,而且 JSON 数据也十分容易阅读,容易理解。

JSON 是一种轻量级的数据交换格式,是 JavaScript 的一个子集,JSON 数据结构只有对象和数组两种结构:

对象:{key:value}
数组:["key","value","string"]

需要注意的一点就是在 JSON 中,对象和数组这两种结构是可以相互嵌套的,比如:

{key:["key","value","string"]}
[{key:value},{key:value},{key:value}]

正是因为可以像这样嵌套使用,JSON 所能表示的信息就十分丰富了。

7.1 fastjson的使用

关于在 Java 对象和字符串之间进行转换的 json 工具有很多,比如 GsonJacksonfastjson 这些,都是可以做到的,但是我们这里主要还是想介绍阿里的 fastjson,其实能够熟练使用一种 json 工具之后,别的工具使用起来也是十分类似的,那我们就先看看使用 fastjson 的基本步骤:

1.导入fastjson的jar包
2.使用相关对象进行Java对象与json字符串之间的转换

Java 对象与 json 字符串之间的转换当中,将 Java 对象转换为 json 字符串是使用的比较多的,因此我们在这里重点就是介绍如何将 Java 对象转换为 json 对象了,为了展示方便,我们先新建一个产品类 (Product)

private int id;

private String name;

private int count;

private double price;

产品类中主要的字段也就是上面这四个了,分别代表的意思为编号、名称、数量和价格,类中对应字段的 gettersetter 方法在这里就不写了,下面就是将一个 Java 对象转为 json 字符串的代码了。

Product product = new Product();
product.setId(1);
product.setName("收音机");
product.setCount(10);
product.setPrice(20);
String json = JSONObject.toJSONString(product);
System.out.println(json);

这样我们就能看到 Java 对象转换得到的 json 字符串了。

{"count":10,"id":1,"name":"收音机","price":20}

这样就得到我们所需要的 json 字符串了,其实想将 json 字符串转换为 Java 对象也是十分简单的,如:

Product pp = JSONObject.parseObject(json, Product.class);
System.out.println(pp);

这样的话,我们就能将刚才得到的 json 字符串转换为 Java 对象了,打印出来的结果就是该 Java 对象的地址了。

party.laucloud.domain.Product@1500955a

上面就是将一个 Java 对象转换为 json 字符串的过程了,下面我们再看如果是一个 List 集合呢?集合中包含多个 Java 对象,又如何转换为 json 对象呢?

Product product = new Product();
product.setId(1);
product.setName("收音机");
product.setCount(10);
product.setPrice(20);

Product tvProduct = new Product();
tvProduct.setId(2);
tvProduct.setName("电视机");
tvProduct.setCount(30);
tvProduct.setPrice(200);

List<Product> products = new ArrayList<>();
products.add(product);
products.add(tvProduct);
String json = JSONObject.toJSONString(products);
System.out.println(json);

最后打印的结果会是怎样的呢?因为在 List 集合中有多个 Java 对象,因此最后得到的结果应该是一个数组,然后其中包含多个对象。

[{"count":10,"id":1,"name":"收音机","price":20},{"count":30,"id":2,"name":"电视机","price":200}]

7.2 使用fastjson中的一些问题

在使用 fastjson 的过程中,我们可能会碰到一些问题,将问题在这里记录下来,可以使自己的记忆更加深刻,而且以后碰到类似的问题也好查询,下面看具体的问题。

7.2.1 指定属性别名和时间属性格式

在我们上面的例子中,生成 json 数据时,所得到的 json 文本都是直接根据 Java 对象的属性名以及对应的属性值直接转换得到的,比如在 Java 对象中有一个 id 属性,那在转换得到的 json 文本中有一个键值对的键就一定是 id 了,但是如果我们不想得到的是 id 呢?而是对应的中文名称,比如编号,那应该怎么办呢?其实就可以在 Java 类的 id 属性上面加一个注解,那就是

@JSONField(name = "编号")

那我们得到的 json 结果就是下面这样的了。

{"count":10,"name":"收音机","price":20,"编号":1}

还有一个就是关于时间字段的格式问题,比如产品类可能不只有上面我们说的 4 个属性,可能还有一个生产日期属性 (productDate)

private Date productDate;

这样的话,当我们直接生成 json 字符串时,结果就会是这样的了:

{"count":10,"name":"收音机","price":20,"productDate":1549978370621,"编号":1}

可以发现,productDate 所对应的值为 1549978370621,很明显是一个毫秒数,如果直接这样展示的话,我们是不好理解的,那要怎么办,才能将该生产日期转换为我们容易理解的格式呢?其实也可以通过上面的注解,如下:

@JSONField(format = "yyyy-MM-dd")

就是在 productDate 字段的上面加上该注解,这样就能得到指定时间格式的 json 字符串了。

{"count":10,"name":"收音机","price":20,"productDate":"2018-02-12","编号":1}
7.2.2 指定Java对象中的哪些属性生成在json中

有时候我们不想将 Java 对象中所有的属性都转化到 json 字符串当中,比如编号,那我们应该怎么办呢?

其实 JSONObject 类中的 toJSONString() 是有很多重载方法的,除了直接将需要转换的 Java 对象传入该方法之后,它还有一个重载方法,可以传入一个 SerializeFilter 对象,这个对象就可以对哪些属性可以生成在 json 字符串中进行过滤操作,这样我们就能完成我们想要的功能了。

SerializeFilter 是一个接口,它有一个子接口为 PropertyFilter,我们就通过实例化 PropertyFilter 的一个匿名内部类来完成相关功能,实现该接口需要实现它其中的一个抽象方法 apply(),具体的代码如下:

SerializeFilter filter = new PropertyFilter() {

    @Override
    public boolean apply(Object arg0, String arg1, Object arg2) {
        System.out.println(arg0);
        System.out.println(arg1);
        System.out.println(arg2);
        if ("编号".equals(arg1)) {
            return false;
        }
        return true;
    }
};
String json = JSONObject.toJSONString(product, filter);

要想搞清楚 apply() 方法的作用,那我们就需要先知道方法中三个参数的作用了,我们可以像上面的代码这样将三个参数都打印出来,这样就能知道各个参数的意思了。

party.laucloud.domain.Product@42f93a98
count
10
party.laucloud.domain.Product@42f93a98
name
收音机
party.laucloud.domain.Product@42f93a98
price
20.0
party.laucloud.domain.Product@42f93a98
productDate
Tue Feb 12 22:14:57 CST 2019
party.laucloud.domain.Product@42f93a98
编号
1

从上面的结果就能知道,三个参数的意义了,第一个参数就是类对象本身了,第二个参数则是类对象中的属性名称了,最后第三个参数则是对应的属性值了,由此便可以知道其实 apply() 方法的作用就是,遍历类对象中的各个属性,每个属性都要过一遍 apply() 方法,如果该方法是返回的 false,那就表示对应的属性将不会出现在生成的 json 字符串当中,如果返回的是 true ,则表示该属性会出现在生成的 json 字符串当中。

因此我们现在看上面的代码的话,就能看出编号是不会出现在生成的 json 字符串当中的,因为当属性名称为编号时,apply() 方法会返回 false,而其它的都会返回 true

{"count":10,"name":"收音机","price":20,"productDate":"2019-02-12"}

上面的方法是不是感觉使用起来会有点繁琐,其实还有一中简便方法,那就是 SerializeFilter 是有一个实现类的,那就是 SimplePropertyPreFilter,该类有一个构造方法,将我们需要在生成 json 中显示的属性名称传进去,我们就能在生成的 json 字符串中看到,没有传入的属性名就不会出现在生成的 json 字符串当中。

SerializeFilter filter = new SimplePropertyPreFilter("name", "count", "price", "productDate");
String json = JSONObject.toJSONString(product, filter);

这样同样也能实现我们想要的功能的。

7.2.3 Java对象转换成json字符串时的循环引用问题

上面在介绍将 Java 对象转换为 json 字符串时,已经介绍了将 List 集合对象转换为 json 字符串,这里需要注意的一种情况就是,如果集合中装入的多个对象都是一个对象,那生成的 json 字符串会是怎样的呢?

Product product = new Product();
product.setId(1);
product.setName("收音机");
product.setCount(10);
product.setPrice(20);
product.setProductDate(new Date());

List<Product> products = new ArrayList<>();
products.add(product);
products.add(product);
products.add(product);

String json = JSONObject.toJSONString(products);
System.out.println(json);

得到的结果应该是怎样的呢?

[{"count":10,"name":"收音机","price":20,"productDate":"2019-02-12","编号":1},{"$ref":"$[0]"},{"$ref":"$[0]"}]

我们可以看到,生成的 json 字符串当中,数组中的第一个对象是和以前一样根据单个 Java 对象生成的 json 字符串,但是第二个和第三个对象则是 {"$ref":"$[0]"},而不是和第一个一样是完整的消息,这就是因为集合中装入的对象都是一样的,所以在生成 json 字符串时,第二个和第三个对象就简化为了引用的形式,那要解决这个问题应该怎么办呢?

其实解决办法也十分简单,还是 JSONObject 类中的 toJSONString() 方法,有一个重载方法,可以传入一个 SerializerFeature 对象,SerializerFeature 对象中有一个属性为 DisableCircularReferenceDetect,就可以使循环引用失效了。具体代码为:

String json = JSONObject.toJSONString(products, SerializerFeature.DisableCircularReferenceDetect);

这样就可以完成我们想要的功能了。

8.以json格式返回响应数据案例

我们在上面已经学习了使用 jQuery 进行 Ajax 请求开发和 json 数据格式了,那我们现在就可以结合这两点来做一些小的案例来加深我们的印象。

8.1 显示商品信息

我们想要的效果就是,在页面中有一个名称为显示商品信息的按钮,当我们第一次点击该按钮时,会向后台发送一次 Ajax 请求,获取得到商品信息,然后在前台页面中进行展示,当我们再点击一次页面中的显示商品信息按钮时,商品信息会隐藏,再点击一次页面中的按钮,就又会和第一次点击按钮一样,显示出商品信息,依次循环。这便是我们想要的效果。

要完成上面所说的切换效果,我们可以使用 jQuery 中的 toggle() 方法进行事件的切换,就是 toggle() 方法中的函数会依次循环执行,那我们可以借助于这一功能,传入两个函数,第一个是向后台发送 Ajax 请求,获取得到商品信息,然后在前端页面中进行展示,第二个函数就是将展示的商品信息进行隐藏,这样的话,我们就能实现想要的功能了。

首先我们先看页面中应该有的元素,当然是需要有一个点击按钮,然后就是一片用于显示商品信息的区域了。

<body>
    <a href="javascript:void(0)" id="link">显示商品信息</a>
    <div id="content"></div>
</body>

接着我们再看后台的 Servlet 应该如何组织返回响应数据,其实也是比较简单的,就是在一个 List 集合中包含多个商品信息,然后再将该集合转换为 json 数据响应到前端就好了。

public class ProductServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 防止乱码
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());

        Product product = new Product();
        product.setId(1);
        product.setName("收音机");
        product.setCount(10);
        product.setPrice(20);
        product.setProductDate(new Date());

        Product tvProduct = new Product();
        tvProduct.setId(2);
        tvProduct.setName("电视机");
        tvProduct.setCount(30);
        tvProduct.setPrice(200);
        tvProduct.setProductDate(new Date());

        List<Product> products = new ArrayList<>();
        products.add(product);
        products.add(tvProduct);
        String json = JSONObject.toJSONString(products);
        response.getWriter().write(json);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

最后就是为页面中的按钮添加事件了,当我们第一次点击时显示商品信息,第二次点击时则会隐藏商品信息,先看完整的代码:

<script type="text/javascript">
    $(function() {
        $("#link").toggle(function() {
            var url = "/jqueryAjax/productServlet";
            $.post(url, function(data) {
                $("#content").html("");
                var json = eval(data);
                var tab=$("<table border='1'><tr><td>编号</td><td>名称</td><td>数量</td><td>价格</td></tr></table>");
                for(var i=0;i<json.length;i++){
                    var obj = json[i];
                    var tr = $("<tr><td>"+obj.id+"</td><td>"+obj.name+"</td><td>"+obj.count+"</td><td>"+obj.price+"</td></tr>");
                    tab.append(tr);
                }
                $("#content").append(tab);
                $("#content").show();
            }, "json");
        }, function() {
            $("#content").hide();
        });
    });
</script>

看代码的话就会发现,其实重点就在于第一个函数,发送 Ajax 请求获取得到商品信息,然后将后端响应回来的数据解析展示在前端页面,这里发送 Ajax 请求时就是使用的 $.post() 方法,可以看到使用起来也是非常简单,需要注意设置响应回来数据的格式为 json,接着的重点就是解析数据然后进行展示了,这里我们是使用的 eval() 方法将响应数据解析为 JavaScript 对象的,然后遍历该对象包含多少个商品信息,就拼接多少个表格对象中的 tr 对象,然后将这些 tr 对象再全部追加到表格对象 table 中,最后将表格对象追加到用于展示商品信息的 div 中,这样就完成了我们所需要的功能。

8.2 省市二级联动

下面我们再做一次省市二级联动的案例,不过这次后端响应数据的格式是 json 格式的,还是在页面中有两个下拉框,第一个下拉框为省份下拉框,第二个下拉框为城市下拉框,当页面加载完成时,就会向后台发送一次 Ajax 请求,获取得到省份信息,然后响应 json 数据回前端,前端页面进行解析并展示,而当我们选择省份下拉框中的选项时,也会向后台发送一次 Ajax 请求,这次请求会带上我们刚才在省份下拉框选中的省份名称,然后获取得到对应的城市信息,也是转换为 json 格式的数据,然后响应给前端页面,前端页面解析后台响应数据并将城市信息展示到城市下拉框中。

首先我们看前端页面所需要的网页元素,就是两个下拉选择框,一个省份下拉框,还有一个城市下拉框,具体代码为:

<select id="province">
    <option>--请选择省份--</option>
</select>省

<select id="city">
    <option>--请选择城市--</option>
</select>市

这样的话,就可以在页面中显示两个下拉选择框了,接着我们再来看看省份以及对应的城市信息在后台应该如何存储,我们使用 Province 类来表示省份信息,用 City 类来表示城市信息,那我们就可以使用一个 Map 集合来保存所有的省份以及对应的城市信息了,也就是:

Map<Province, List<City>>

而且 Province 类和 City 类的主要字段都是为:

private int id;

private String name;

因为我们是使用 Servlet 来处理前端请求的,因此我们可以在 Servletinit() 方法中加载好所有的省份以及城市信息,后面需要时就可以直接使用了,代码如下:

public class CityServlet extends HttpServlet {

    private Map<Province, List<City>> map = new HashMap<>();

    @Override
    public void init() throws ServletException {
        Province province = new Province();
        province.setId(1);
        province.setName("湖北");

        City c1 = new City();
        c1.setId(1);
        c1.setName("武汉");

        City c2 = new City();
        c2.setId(2);
        c2.setName("天门");

        List<City> cityList = new ArrayList<>();
        cityList.add(c1);
        cityList.add(c2);
        map.put(province, cityList);

        Province scProvince = new Province();
        scProvince.setId(2);
        scProvince.setName("四川");

        City c3 = new City();
        c3.setId(3);
        c3.setName("成都");

        City c4 = new City();
        c4.setId(4);
        c4.setName("都江堰");

        List<City> scCityList = new ArrayList<>();
        scCityList.add(c3);
        scCityList.add(c4);
        map.put(scProvince, scCityList);
    }
}

前端过来的对于省份信息和城市信息的请求,我们都会使用 CityServlet 来处理,但是我们前面分析的时候就知道,是有两次请求的,一次是请求省份信息,另一次则是请求城市信息,那我们在后端应该如何区分这两次请求呢?其实很简单,可以在前端请求时加入一个参数 method,当请求省份信息时,将该属性的属性值赋为 province,当请求某个省份所对应的城市信息时,将该属性的属性值赋为 city,这样在后端我们就可以区分两次请求了。

后端具体的处理逻辑代码为:

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // 防止乱码
    request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());

    String method = request.getParameter("method");
    if ("province".equals(method)) {
        Set<Province> keySet = map.keySet();
        String json = JSONObject.toJSONString(keySet);
        response.getWriter().write(json);
    }

    if ("city".equals(method)) {
        String province = request.getParameter("province");
        for (Province pro : map.keySet()) {
            if (province.equals(pro.getName())) {
                List<City> citys = map.get(pro);
                String json = JSONObject.toJSONString(citys);
                response.getWriter().write(json);
                break;
            }
        }
    }
}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    doGet(request, response);
}

最后我们再来看前端向后端发送 Ajax 请求的代码了,其实也是十分简单的了,页面加载完成后就向后台发送一次 Ajax 请求得到省份信息,得到数据后解析并展示在省份下拉框中。

$(function(){
    var url = "/jqueryAjax/cityServlet"
    $.post(url,{"method":"province"},function(data){
        var jsonObj = eval(data);

        for(var i=0;i<jsonObj.length;i++){
            var optionObj = jsonObj[i];
            var option = $("<option>"+optionObj.name+"</option>");
            $("#province").append(option);
        }
    },"json");
});

接着,当我们在省份下拉选择框选择某个省份时,也需要向后台发送一次 Ajax 请求得到对应省份的城市信息,得到数据后解析展示在城市下拉框中。

$("#province").bind("change",function(){

    var province = $(this).val();
    $.post(url,{"method":"city","province":province},function(data){
        $("#city").html("<option>--请选择城市--</option>");
        var cityObj = eval(data);

        $(cityObj).each(function(){
            var cityName = this.name;
            var option = $("<option>"+cityName+"</option>");
            $("#city").append(option);
        });
    },"json");
});

这样就完成我们想要的功能了。

9.XStream的使用

上面我们介绍了在服务器端将 Java 对象转换为 json 字符串后,响应给前端页面处理,下面我们想要做的就是,如果在服务器端需要响应 xml 格式的数据给前端,我们应该怎么做?这时候我们就可以使用 XStream 了,使用 XStream,我们就可以完成 Java 对象与 xml 格式文本之间的转换,也就可以在服务器端响应 xml 格式的数据了。

在使用 XStream 工具之前,我们要先导入其所需要的 jar 包,一共为 3 个:

xstream-1.4.9.jar
xpp3_min-1.1.4c.jar
xmlpull-1.1.3.1.jar

这样的话,我们就可以使用 XStream 来完成我们想要的功能了,为了更好地看到效果,我们还是使用前面省市二级联动案例中的数据来做演示。

Map<Province, List<City>> map = new HashMap<>();
Province province = new Province();
province.setId(1);
province.setName("湖北");

City c1 = new City();
c1.setId(1);
c1.setName("武汉");

City c2 = new City();
c2.setId(2);
c2.setName("天门");

List<City> cityList = new ArrayList<>();
cityList.add(c1);
cityList.add(c2);
map.put(province, cityList);

Province scProvince = new Province();
scProvince.setId(2);
scProvince.setName("四川");

City c3 = new City();
c3.setId(3);
c3.setName("成都");

City c4 = new City();
c4.setId(4);
c4.setName("都江堰");

List<City> scCityList = new ArrayList<>();
scCityList.add(c3);
scCityList.add(c4);
map.put(scProvince, scCityList);

下面我们就来看如何将上面 Map 集合中的数据转换为 xml 格式的数据,代码也是非常简单:

XStream xStream = new XStream();
String xml = xStream.toXML(map);
System.out.println(xml);

这样就可以得到 xml 格式的数据了,结果为:

<map>
  <entry>
    <party.laucloud.domain.Province>
      <id>2</id>
      <name>四川</name>
    </party.laucloud.domain.Province>
    <list>
      <party.laucloud.domain.City>
        <id>3</id>
        <name>成都</name>
      </party.laucloud.domain.City>
      <party.laucloud.domain.City>
        <id>4</id>
        <name>都江堰</name>
      </party.laucloud.domain.City>
    </list>
  </entry>
  <entry>
    <party.laucloud.domain.Province>
      <id>1</id>
      <name>湖北</name>
    </party.laucloud.domain.Province>
    <list>
      <party.laucloud.domain.City>
        <id>1</id>
        <name>武汉</name>
      </party.laucloud.domain.City>
      <party.laucloud.domain.City>
        <id>2</id>
        <name>天门</name>
      </party.laucloud.domain.City>
    </list>
  </entry>
</map>

下面我们再来看使用 XStream 的过程中的一些常用方法,掌握这些常用方法,可以使我们平时的开发更加高效。

9.1 设置别名

看上面的 xml 数据,我们会发现生成结果的文本中有些信息看起来不是那么直观,比如省份和城市的节点,结果中仍然是以类的全限定名进行展示的,如果我们就想以省份或者城市这样的中文作为节点名称应该怎么做呢?其实在 XStream 类中也提供了十分简便的方法供我们使用。

xStream.alias("省份", Province.class);
xStream.alias("城市", City.class);

不仅可以为我们自己创建的类设置别名,也可以为 List 这样的类设置别名,如:

xStream.alias("城市们", List.class);

当然,除了给类设置别名之外,我们还可以为类中的属性设置别名,比如将 City 类中的 id 属性设置为编号:

xStream.aliasField("编号", City.class, "id");

通过上面的设置,我们就可以看一下现在得到的 xml 数据应该是怎样的。

<map>
  <entry>
    <省份>
      <id>2</id>
      <name>四川</name>
    </省份>
    <城市们>
      <城市>
        <编号>3</编号>
        <name>成都</name>
      </城市>
      <城市>
        <编号>4</编号>
        <name>都江堰</name>
      </城市>
    </城市们>
  </entry>
  <entry>
    <省份>
      <id>1</id>
      <name>湖北</name>
    </省份>
    <城市们>
      <城市>
        <编号>1</编号>
        <name>武汉</name>
      </城市>
      <城市>
        <编号>2</编号>
        <name>天门</name>
      </城市>
    </城市们>
  </entry>
</map>

9.2 忽略属性

在生成 xml 数据的过程中,有时候我们不想将某些属性生成到 xml 文件当中,比如我们不想将 Province 类中的 id 属性生成到 xml 文件当中,那我们可以进行如下操作:

xStream.omitField(Province.class, "id");

生成的结果为:

<map>
  <entry>
    <省份>
      <name>四川</name>
    </省份>
    <城市们>
      <城市>
        <编号>3</编号>
        <name>成都</name>
      </城市>
      <城市>
        <编号>4</编号>
        <name>都江堰</name>
      </城市>
    </城市们>
  </entry>
  <entry>
    <省份>
      <name>湖北</name>
    </省份>
    <城市们>
      <城市>
        <编号>1</编号>
        <name>武汉</name>
      </城市>
      <城市>
        <编号>2</编号>
        <name>天门</name>
      </城市>
    </城市们>
  </entry>
</map>

9.3 将Java类的属性设置为xml中节点的属性

之前在 Province 类中的 id 属性生成到 xml 文件中时,是直接将 id 属性生成到省份节点的下一级的,如:

<省份>
  <id>1</id>
</省份>

如果想要将 id 属性作为省份节点的一个属性呢?那应该怎么办呢?想要的效果就是这样的:

<省份 id="1">
</省份>

其实在 XStream 类中也提供了非常简便的方法供我们使用,就是:

xStream.useAttributeFor(Province.class, "id");

设置完成后,生成的完整 xml 数据应该是这样的:

<map>
  <entry>
    <省份 id="2">
      <name>四川</name>
    </省份>
    <城市们>
      <城市>
        <编号>3</编号>
        <name>成都</name>
      </城市>
      <城市>
        <编号>4</编号>
        <name>都江堰</name>
      </城市>
    </城市们>
  </entry>
  <entry>
    <省份 id="1">
      <name>湖北</name>
    </省份>
    <城市们>
      <城市>
        <编号>1</编号>
        <name>武汉</name>
      </城市>
      <城市>
        <编号>2</编号>
        <name>天门</name>
      </城市>
    </城市们>
  </entry>
</map>

上面三个就是我们想要介绍的在 XStream 中经常使用的方法,掌握了上面三个常用方法对提升我们的开发效率还是很有帮助的。

9.4 XStream的注解使用

其实 XStream 除了上面使用编码的方式来实现相关功能外,也还可以使用注解的方式来完成相关功能。要想能够使用注解的方式,我们首先必须要开启注解扫描功能。

xStream.autodetectAnnotations(true);

设置了之后我们就可以使用注解来完成相关功能了,这里我们也还是演示上面常用的三个方法,只不过这次便是使用注解功能了。

9.4.1 设置别名

使用注解为类或者类中属性设置别名,只需要使用 @XStreamAlias 注解就好了,如为 City 类设置别名。

@XStreamAlias("城市")
public class City {

    @XStreamAlias("编号")
    private int id;

    private String name;
}

这里需要说明的是,类中属性的 gettersetter 方法,我们在这里是没有写的。不管是为类设置别名,还是为类中的属性设置别名,我们都只需要使用 @XStreamAlias 就好了。

9.4.2 忽略属性

使用注解忽略类中的属性,则是需要使用 @XStreamOmitField 注解,比如我们过滤 Province 类中的 id 属性:

@XStreamAlias("省份")
public class Province {

    @XStreamOmitField
    private int id;

    private String name;
}

同样的,类中属性的 gettersetter 方法,我们在这里也是没有写的。通过上面这个注解的设置,在最终生成的 xml 文件中,关于省份信息部分 id 属性则是被忽略的了。

9.4.3 将Java类的属性设置为xml中节点的属性

使用注解将 Java 类的属性设置为 xml 中节点的属性,则可以通过 @XStreamAsAttribute 注解来完成,比如我们想要将 Province 类中的 id 属性设置成在 xml 文件中省份节点中的属性:

@XStreamAlias("省份")
public class Province {

    @XStreamAsAttribute
    private int id;

    private String name;
}

这样我们就完成想要的功能了,Province 类中的 id 属性在生成 xml 文件中的省份节点中便会作为该节点的属性存在了。

9.5 以xml格式响应数据案例

上面学习了如何将 Java 对象转换为 xml 文件之后,我们现在就可以在服务器端响应前端请求时不返回 json 数据,而返回 xml 数据了,这里做的案例就还是省市二级联动的例子,不过这次服务器端响应的数据格式就是 xml 了,需要说明的是保存省份以及对应城市信息,我们还是使用原来的 Map 集合,即保存数据的结构仍然是 Map<Province, List<City>>

这次请求数据时就只发送一次请求了,一次便将所有的省份和城市信息响应给前端页面,然后在前端页面进行解析并进行展示,下面先看后端代码:

if ("xml".equals(method)) {
    XStream xsStream = new XStream();
    xsStream.autodetectAnnotations(true);
    String xml = xsStream.toXML(map);
    response.getWriter().write(xml);
}

也就是将保存信息的 Map 集合直接转换为 xml 格式的数据,然后响应给前端页面了,下面主要看前端页面了。页面中需要的元素还是两个下拉选择框:

<body>
    <select id="province">
        <option>--请选择省份--</option>
    </select>省

    <select id="city">
        <option>--请选择城市--</option>
    </select>市
</body>

在前端如何发送 Ajax 请求,这我们已经非常熟悉了,在这里的话最主要的就是如何解析 xml 文件的数据了,先看完整的代码,然后再进行详细说明。

<script type="text/javascript">
    $(function() {
        var url = "/jqueryAjax/cityServlet"
        $.post(url,{"method":"xml"},function(data){
            console.log(data);
            var xml = $(data);
            var names = xml.find("省份 name");
            names.each(function(){
                var province = $(this).text();
                var proObj = $("<option>"+province+"</option>")
                $("#province").append(proObj);
            });

            $("#province").bind("change",function(){
                $("#city").html("<option>--请选择城市--</option>");
                var province = $(this).val();
                var provinceNode = xml.find("name:contains("+province+")").parent();
                var list = provinceNode.next();
                var citys = list.find("name");
                citys.each(function(){
                    var city = $(this).text();
                    var cityObj = $("<option>"+city+"</option>")
                    $("#city").append(cityObj);
                });
            });
        },"xml");
    });
</script>

首先在页面加载完成之后,就会向后台发送请求获得省份以及城市信息,关键就在于我们如何在响应的文本中查找省份信息和城市信息,然后再将它们展示在页面中,首先看如何获取省份信息,想要获取省份的名称,那就应该是查找 xml 文件中所有省份节点下面的 name 节点,然后再遍历所有的省份名称,将它们都放到省份下拉选择框中,也就是:

var names = xml.find("省份 name");
names.each(function(){
    var province = $(this).text();
    var proObj = $("<option>"+province+"</option>")
    $("#province").append(proObj);
});

接着便是根据省份名称查找对应的城市名称了,我们可以根据省份名称找到对应的 name 节点,然后找到 name 节点的父节点 (省份节点),然后再找紧接着的兄弟节点,也就是包含城市信息的集合节点了,最后找出该集合节点中所有的 name 节点,也就找到了所有的城市信息。

var province = $(this).val();
var provinceNode = xml.find("name:contains("+province+")").parent();
var list = provinceNode.next();
var citys = list.find("name");
citys.each(function(){
    var city = $(this).text();
    var cityObj = $("<option>"+city+"</option>")
    $("#city").append(cityObj);
});

其实在上面的案例中,最重要的部分还是在前端解析服务器端响应回来的 xml 数据,从 xml 文件中找到我们想要的信息进行展示就好了。

10.总结

这篇博客主要介绍的便是如何发送 Ajax 请求了,最开始介绍的是用 XMLHttpRequest 对象来完成的方式,接着便介绍了使用 jQuery 发送 Ajax 请求的方式,同时还介绍了 json 数据格式,我们在前端发送 Ajax 请求之后可以接受并处理 json 格式的数据,最后也介绍了以 xml 作为响应数据的格式,其实只要多去实现,还是很简单的。

坚持原创技术分享,您的支持将鼓励我继续创作!