第12章 AJAX¶
第 12 章 AJAX¶
AJAX 的全称为 “Asynchronous JavaScript And XML”。AJAX 指的是异步的 JavaScript 和 XML。它是指一种创建交互式网页应用的网页开发技术。简单来说,AJAX 是一种用于创建快速动态网页的技术,它可以让开发者只向服务器端获取数据,需要注意的是,它是让开发者向服务器端获取数据,而不是图片、HTML 文档等。互联网资源的传输变得前所未有得轻量级和纯粹,这激发了广大开发者的创造力,使各式各样功能强大的网络站点,和互联网应用如雨后春笋一般冒出,不断带给人惊喜。本章内容主要包括 AJAX 渲染、同步与异步,以及 Axios 基本用法,响应 JSON 格式数据等。
12.1 AJAX 简介¶
2005 年,Jesse James Garrett 提出了 AJAX 这个新术语。AJAX 中的 A 是 Asynchronous 的首字母,表示异步的意思。也就是说,AJAX 可以发起异步的 HTTP 请求。AJAX 中的 X 是 XML 的首字母,XML 意为可扩展标记语言,最开始时作为后端向前端响应数据的文档格式,但是 XML 目前已经很少使用,在 AJAX 中,JSON 已经代替了 XML。
简而言之,AJAX 是使用 XMLHttpRequest 对象与服务器端通信的脚本语言。它可以发送及接收各种格式的信息,包括 JSON、XML、HTML 和文本文件。
试想,当用户触发一个 HTTP 请求到服务器端时,传统的 Web 应用交互会这样做:服务器端对请求进行处理,然后再返回一个新的 HTML 页面到客户端。每当服务器端处理客户端提交的请求时,客户都只能空闲等待。哪怕只是一次很小的交互,只需要从服务器端得到很简单的一个数据的情况,都要返回一个完整的 HTML 页面。而用户每次都需要浪费时间和带宽去重新读取整个页面。这样一来,由于每次应用的交互都需要向服务器端发送请求,应用的响应时间就依赖于服务器端的响应时间,从而浪费了大量带宽,而且导致了用户界面的响应速度比本地应用的更慢。
而 AJAX 与此不同,AJAX 应用可以仅向服务器端发送并取回必需的数据,同时在客户端采用 JavaScript 处理来自服务器端的响应。因为服务器端和浏览器之间交换的数据大量减少,所以用户就能看到响应更快的应用。同时很多的处理工作可以在发出请求的客户端机器上完成,因此对于速度而言,Web 服务器端的处理时间也减少了。
简单来说,使用 AJAX 技术,可以在页面不刷新或不跳转的前提下向服务器端发起 HTTP 请求,获取响应数据,将增量更新呈现在界面上。最后将 AJAX 技术的优势具体总结如下。
例如,搜索引擎页面中的搜索框就是一种典型的 AJAX 应用场景,如图 12-1 所示。

当用户输入关键字之后,会通过 AJAX 技术向后端请求数据。在不刷新页面的前提下,获取与关键字关联的历史搜索词,并展现在页面中,如图 12-2 所示。

前面学习了 Thymeleaf 服务器端渲染,Thymeleaf 是一个现代化的、渲染 XML/XHTML/HTML5 等内容的、服务端的 Java 模板引擎。它的主要作用是在静态页面上渲染显示动态数据。渲染之后,浏览器的页面会整体刷新,呈现的是同步效果。如果想要实现局部刷新,那么需要借助本章学习的内容 ——AJAX 来进行渲染。相比服务器端渲染,AJAX 的请求是由 JavaScript 发出的,而不是浏览器,并且后台处理过程和服务器端渲染是一样的,只是响应结果直接返回给 JavaScript 代码,从而对页面进行 DOM 操作,因此局部更新不影响其他内容,这就是呈现的异步效果。服务器端渲染过程如图 12-3 所示。AJAX 渲染过程如图 12-4 所示。


同步和异步是一对相对的概念,那么什么是同步,以及什么是异步呢?下面将具体讲解。
12.2 同步与异步¶
因为 JavaScript 在同一时间只能处理一个任务,所有任务都需要排队,前一个任务执行完,才能执行下一个任务。如果前一个任务的执行时间很长,如执行 AJAX 操作或定时器操作时,后一个任务需要等它执行完毕才能向下执行,此时下面的任务就会被阻塞。拿定时器来说,当用户向后台获取大量的数据时,就需要等到所有数据都获取完毕才能进行操作,用户只能在那里干等着,这种阻塞对用户来说意味着 “卡死”,严重影响用户体验。在设计的时候,布莱登・艾奇就考虑到这个问题,将任务分为同步任务(synchronous)和异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行后一个任务。异步任务指的是,不直接进入主线程执行,而是进入任务队列,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程。简单地说,同步任务执行之后,后面的任务必须等待同步任务得到计算结果并处理完成后才能执行;而异步任务在启动执行之后,后面的任务可以继续执行,当异步任务得到计算结果之后,会通知到主线程进行处理。
结合实际来说,如果在函数返回的时候,调用者还无法得到预期结果,而是要在将来通过一定的手段才能得到(如回调函数),这就是异步。例如,AJAX 操作。如果函数是异步的,发出调用之后,就会马上返回,但是不会马上返回预期结果。调用者不必主动等待,当被调用者得到结果之后,会通过回调函数主动通知调用者。
我们通过一个例子来描述同步和异步的区别,假如顾客去饭店吃饭,要点三道菜,分别是酸菜鱼、烤串和香菇炒油菜。若采用同步的方式进行处理,则顾客先向服务员点酸菜鱼,厨师先将酸菜鱼做好,然后吃完后再点烤串,烤串做好并吃完后再点香菇炒油菜,最后香菇炒油菜做好并吃完。整个过程中,不管上菜的时间有多久,都会等待上一道菜做好并吃完后,再点下一道菜,如图 12-5 所示。


若采用异步的方式进行处理,则顾客同时向服务员点酸菜鱼、烤串、香菇炒油菜,哪道菜做好就先上哪道菜。这样一来,即使后厨在制作其中一道菜时浪费了时间,也并不会影响另外两道菜的烹饪,如图 12-6 所示。做好哪道菜就上哪道菜,例如,上菜顺序如图 12-7 所示。
从前面的描述中可以得出结论:异步可以充分发挥计算机性能,使其不会因为执行一个耗时的任务导致后续代码被阻塞。可以想象,如果网页中需要获取一个网络资源,通过同步的方式获取,那么 JavaScript 需要等待资源完全从服务器端获取之后才能继续执行。这期间 UI 渲染也将停顿,这样的用户体验是非常不好的。而采用异步的方式获取,在下载网络资源期间,
JavaScript 和 UI 渲染都不会处于等待状态。

在 JavaScript 中,定时器、DOM 事件和 AJAX 等的执行方式都是异步的。需要注意的是,异步方式执行的 JavaScript 代码仍然是单线程运行的。当异步任务计算完成,得到结果并通知主线程之后,也需要等
待其他任务完成,主线程空闲时才能进行相关处理。
对比同步和异步可知,同步操作通常是串行的,多个操作按顺序执行,前面的操作没有完成,后面的操作就必须等待,如图 12-8 所示。而对于异步操作来说,多个操作相继开始并发执行,即使开始的先后顺序不同,但是由于它们各自是在自己独立的进程或线程中完成的,所以互不干扰,谁也不用等谁,如图 12-9 所示。


在实际应用中,AJAX 指的是不刷新浏览器窗口、不做页面跳转、局部更新页面内容的技术。真正的前后端分离是前端项目和后端项目分服务器端部署,在这里可以先理解为彻底舍弃服务器端渲染,数据全部通过 AJAX 方式以 JSON 格式来传递。接下来会对 AJAX 应用展开详细介绍。
12.3 XMLHttpRequest 对象¶
AJAX 的技术核心就是 XMLHttpRequest 对象。当我们在 JavaScript 中发送一个 HTTP 请求或接收响应时,就会使用 XMLHttpRequest 对象。
XMLHttpRequest 对象是通过实例化 XMLHttpRequest () 构造函数的方式来创建的,示例代码如下。 var xhr = new XMLHttpRequest ();
那么,一个完整的 AJAX 是怎么通过 XMLHttpRequest 对象实现的呢?
其实使用 XMLHttpRequest 对象实现 AJAX 可以分为五步,具体如下。
(1)创建 XMLHttpRequest 对象。
(2)为 XMLHttpRequest 对象监听进度事件,由于是异步操作,等到接收响应数据后,事件才会触发。
(3)初始化请求并指定请求方式。
(4)发送请求并设置数据体。
(5)服务器端成功响应,触发进度事件,执行事件的回调函数,接收响应数据,并对响应数据进行相关处理。
明确使用 XMLHttpRequest 对象实现 AJAX 的五步后,先来整体 “感知” 发送 AJAX 请求并接收响应进行处理的案例。
HTML 文件的示例代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="vue.js"></script>
</head>
<body>
<div id="app">
<h1>测试 XMLHttpRequest</h1>
<input type="button" value="点我发送请求" @click="fun01"/></br/>
</div>
<script>
new Vue({
el:"#app",
data: {},
methods: {
fun01:function () {
alert("测试 XMLHttpRequest");
// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
// 初始化 GET 请求
xhr.open("GET", "xmlHttpRequestTest?message=hello");
// 监听进度事件,待响应完成后获取响应内容
xhr.onreadystatechange = function () {
// 如果请求还没有返回,就直接结束
if (xhr.readyState !== 4) return;
// 响应状态码在 200~299 都代表请求成功
if (xhr.status >= 200 && xhr.status <= 299) {
// 获取所有的响应头信息
console.log(xhr.getAllResponseHeaders());
// 获取指定的响应头信息
console.log(xhr.getResponseHeader("Content-Length"));
// 获取响应体文本
console.log(xhr.responseText);
}
};
// 发送请求
xhr.send();
}
}
});
</script>
</body>
</html>
XMLHttpRequestTestServlet 示例代码如下。
package com.atguigu.servlet;
//省略 import 语句
@Servlet("/xmlHttpRequestTest")
public class XMLHttpRequestTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问到了 AJAXTestServlet");
String message = req.getParameter("message");
System.out.println("message = " + message);
//响应数据:不能用重定向和转发(属于同步请求),采用输出流的方式
PrintWriter writer = resp.getWriter();
//谁发请求,数据就响应给谁(JavaScript)
writer.write("success");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
运行代码查看页面效果,如图 12-10 所示。

在此页面下按 F12 键,单击超链接发送请求,查看控制台响应数据,如图 12-11 所示。
XMLHttpRequest 一开始只是微软浏览器提供的一个接口,后来各大浏览器纷纷效仿也提供了这个接口,再后来 W3C 对它进行了标准化,提出了 XMLHttpRequest 标准。XMLHttpRequest 标准分 level 1 和 level 2 两个版本,level 2 版本是对 level 1 版本的升级和改进。简单地说,level 2 版本在原 API 的基础上新增了更多的 API。目前 level 2 版本中新增的 API 已经得到了各大浏览器的广泛支持。

XMLHttpRequest level 1 主要存在以下缺点。
level 2 版本在 level 1 版本的基础上进行了改进,XMLHttpRequest level 2 中新增了以下功能。
12.4 Axios 简介¶
使用原生的 JavaScript 程序执行 AJAX 的过程极其烦琐,为了解决 XMLHttpRequest 的 AJAX 请求封
装的复杂性,以及便于后续增加请求响应等拦截器的常用功能,还可以考虑使用第三方类库 Axios。
Axios 是目前最流行的前端 AJAX 框架。Axios 是一个具有独立开发功能且目标明确的请求类库,它基于 Promise,是一个既可用于浏览器又可以用于 Node.js 服务器端的 HTTP 请求模块。本质上它是符合最新 ES 规范,使用 Promise 实现的、原生 XHR 的封装。在服务端,它使用原生 Node.js 的 HTTP 模块实现;而在客户端,它则使用 XMLHttpRequest 对象实现。因为对于请求操作,Axios 进行了常用功能的封装,所以开发人员若想实现网络请求功能,则只需要直接安装使用它,就可以拥有它带来的诸多新的特性内容,主要包括以下几点。
12.5 Axios 入门案例¶
下面通过一个案例简单演示 Axios 的使用。在页面创建一个按钮,单击按钮发送异步请求,触发后台 Servlet 代码,并实现获取请求参数,在控制台输出。
采用 Axios 异步框架向后台发送请求的步骤如下。
HTML 页面如下,发送异步请求到服务器端(Servlet)。
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/chapter12 AJAX_war_exploded/">
<meta charset="UTF-8">
<title>首页</title>
<!--导入框架代码-->
<script src="vue.js"></script>
<script src="axios.min.js"></script>
</head>
<body>
<div id="app">
<h1>首页</h1>
<input type="button" value="点我发送异步请求" @click="fun01"/>
</div>
<script>
new Vue({
</body>
</html>
el:"#app",
data:{},
methods:{
fun01:function () {
alert(100);
//发送异步请求到服务器端(Servlet)
//采用 Axios 异步框架(JavaScript 框架,专门发送异步请求)
//设置请求发送到哪里,以及携带什么参数(以 JSON 格式传递)
axios({
method:"post",//设置请求方式
url:"AJAXTest",//设置请求路径
//设置请求参数
params:{
message:"hello,AJAX!"
}
}).then(response => {
//程序正常运行的回调函数
//获得服务器端传过来的数据(response.data)
var msg = response.data;
alert("服务器端响应数据为:"+msg);
//接下来就可以根据响应数据进行DOM操作(局部操作)
}).catch(error => {
//代码出现异常的回调函数
alert("服务器端出现异常了");
});
}
});
</script>
</body>
</html>
创建 AJAXTestServlet,继承 HttpServlet,获取请求参数并返回响应数据。
package com.atguigu.servlet;
//省略 import 语句
@WebServlet("/AJAXTest")
public class AJAXTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问到了 AJAXTestServlet");
String message = req.getParameter("message");
System.out.println("message = " + message);
//响应数据:不能使用重定向和转发(属于同步请求),采用输出流的方式
PrintWriter writer = resp.getWriter();
//谁发请求,数据就响应给谁(JavaScript)
writer.write("success");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
运行代码,Axios 的入门案例首页如图 12-12 所示。
单击按钮,查看后端控制台,如图 12-13 所示。同时再次查看前端页面,如图 12-14 所示,成功接收到后台返回的响应数据。



12.6 Axios 基本用法¶
Axios 的使用方法与 Vue 类似,直接在页面导入对应的 *.js 文件即可。官方提供的借助 <script> 标签的引入方式如下。
我们可以下载 axios.min.js 文件,并将其保存到本地使用。在前端页面引入开发环境,具体如下。
<!-- 引入Vue -->
<script type="text/javascript" src="vue.js"></script>
<!-- 引入Axios -->
<script type="text/javascript" src="axios.min.js"></script>
使用 Axios 发送异步请求,直接借助 axios () 函数,函数中可以传入 JSON 数据,设置请求方式、请求路径,以及携带什么参数等。
//使用 Axios 发送异步请求
axios({
"method": "post", // 设置请求方式为 post 请求
"url": "demo01", // 设置请求路径
// 设置请求参数
"params": {
"userName": "tom",
"userPwd": "123456"
}
})
axios () 函数的参数可以先定义再传入,而且请求方式还可以简化,示例代码如下。
var url="请求地址";
var data={}
//发送 post 异步请求
axios.post(url, data).then(response => {
//response 是服务器端的响应数据,是 JSON 类型
this.message = response.data
}).catch(error => {
//error 就是出错时服务器端返回的响应数据
console.log(error)
})
其中,then () 函数和 catch () 函数分别是服务器端处理请求成功和失败后调用的函数。
then () 函数,一般用于处理请求成功的响应数据。它是异步执行,通常用在 AJAX 请求后面,即当.then () 前的方法执行完后再执行 then () 内部的程序,这样一来就能避免出现数据没获取到的问题。上述代码中 response 对象封装了服务器端的响应数据,类型是 JSON 格式,response 的 data 属性存储的是响应体的数据。
其实,then () 函数包含两个参数,一个是请求成功时的回调方法,默认给该方法传递成功的数据;另一个是请求失败的方法,用于处理请求失败的相关数据。前者是响应,响应数据存储在参数 response 中,而后者是错误,错误信息封装在参数 error 中。
var url="请求地址"
axios.post(url, {username:"tom"})
//链式编程
.then(response => { //成功回调方法
this.msg = response.data;
}, error => { //异常回调方法
console.log(error);
})
一般情况下,为了避免程序报错,会在 then () 后面调用.catch () 函数,这与 try-catch 方法相似,可以理解为省略了 try () 部分。
值得注意的是,如果在 then () 函数的第一个方法参数中抛出了异常,那么后面的 catch () 函数可以捕获到,但 then () 函数的第二个方法参数抛出异常却无法捕获。因此,建议使用 catch () 函数处理请求失败信息,而不使用 then () 函数的第二个方法参数。
catch () 函数传入的回调函数,error 对象封装了服务器端处理请求失败后相应的错误信息。其中,Axios
封装的响应数据对象,是 error 对象的 response 属性。response 属性对象的结构如图 12-15 所示。可以看到,response 属性对象的结构,与 then () 函数传入的回调函数中的 response 属性对象的结构是一样的。
注意,回调函数是指开发人员声明,但是交给系统来调用的函数。像单击响应函数、then () 函数、catch () 函数等传入参数的都是回调函数。回调函数是相对于普通函数来说的,普通函数是指开发人员声明,并且开发人员自己调用的函数。示例代码如下。

//声明函数
function sum(a, b) {
return a + b;
}
//调用函数
var result = sum(3, 2);
console.log("result=" + result);
12.7 AJAX 响应复杂数据¶
通过前面的学习,我们可以将普通字符串作为 AJAX 的响应数据进行传递,如果需要响应 JavaBean、List 或 Map 等复杂数据,那么应该如何处理呢?AJAX 在响应复杂数据时,首先需要服务器端将复杂数据转换为 JSON 字符串,然后才能进行传递。
服务器端在将复杂数据转换为 JSON 字符串时,需要借助 Gson 工具,具体步骤如下。
值得注意的是,服务器端响应 JSON 字符串回到页面,实际上,异步请求 JavaScript 端接收到的数据是 JSON 对象。Axios 框架会判断字符串是否符合 JSON 格式要求,如果符合,就自动转为 JSON 对象;如果不符合,就仍为字符串。下面分别介绍 AJAX 如何响应 JavaBean、List 集合和 Map 三种复杂数据。
12.7.1 JavaBean 作为响应数据¶
当 JavaBean 作为响应数据时,以 User 对象为例进行演示。
User 类包含三个属性,分别为 id、username 和 password,示例代码如下。
package com.atguigu.bean;
public class User {
private Integer id;
private String username;
private String password;
//......
}
首先,创建 JsonTestServlet,将 User 对象转换为 JSON 字符串格式,并响应数据到前端页面,示例代码如下。
package com.atguigu.servlet;
//省略 import 语句
@Servlet("/jsonTest")
public class JsonTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问到了JsonTestServlet");
//将普通的 JavaBean 作为响应数据
User user = new User(1, "admin", "root");
//将 User 转换为 JSON 字符串(满足 JSON 格式的字符串)
//采用工具 Gson (Java 端的工具,是一个 jar 包)
Gson gson = new Gson();
String s = gson公斤User);
System.out.println(s); //{{,}
PrintWriter writer = response.getWriter();
writer.write(s);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
其次,在前端页面接收 JSON 格式的 User 对象。
HTML 代码如下。
<script>
new Vue({
el:"#app",
data: {},
methods: {
fun02:function () {
//采用 Axios 发送异步请求
axios({
method:"post",//设置请求方式
url:"jsonTest",//设置请求路径
//设置请求参数
params: {}
}).then(response => {
/*后台写回来的是 JSON 字符串,Axios 框架会判断字符串是否符合 JSON 格式要求。如果符合就自动转为 JSON 对象,如果不符合就仍为字符串*/
//JavaBean
var obj = response.data;
console.log(obj.id);
console.log(obj.username);
console.log(obj.password);
});
}
}
});
</script>
运行代码后,单击 “点我发送异步请求” 按钮,查看前端控制台,如图 12-16 所示。已成功获取 User 对象的所有属性值,另外查看后端控制台,可以看到传递回前端的 User 对象的类型为 JSON 字符串格式,如图 12-17 所示。

图 12-16 响应 JavaBean 的结果 图 12-17 User 对象转换为 JSON 字符串格式
| Deployment | Output | |
| x √ web_axaxwar exploded | ↑ | 访问到了JsonTestServlet {"id":1,"username":"admin","password":"root"} |
| » | » | |
12.7.2 List 集合作为响应数据¶
当 List 集合作为响应数据时,以包含多个 User 对象的集合为例进行演示。
修改 JsonTestServlet,将包含多个 User 对象的 List 集合转换为 JSON 字符串格式,并响应数据到前端页面,示例代码如下。
package com.atguigu.servlet;
//省略 import 语句
@WebServlet("/jsonTest")
public class JsonTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问到了 JsonTestServlet");
//创建 User 对象
User user = new User(1, "admin", "root");
//将 List 集合作为响应数据
List<Object> list = new ArrayList<>();
list.add(new User(1, "admin1", "root"));
list.add(new User(2, "admin2", "root"));
list.add(new User(3, "admin3", "root"));
//借助 Gson 对象将 List 集合转换为 JSON 字符串格式
String s = new Gson().toJson(list);
System.out.println("s = " + s); // [{{}, {}}, {}]
response.getWriter().write(s);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
前端 JavaScript 代码如下。
<script>
new Vue({
el:"#app",
data: {},
methods:
fun02:function () {
//采用 Axios 发送异步请求
axios({
method:"post",//设置请求方式
url:"jsonTest",//设置请求路径
//设置请求参数
params: {}
}).then(response => {
//List
var arrs = response.data;
for (var i = 0; i < arrs.length; i++) {
console.log(arrs[i].id);
console.log(arrs[i].username);
}
});
}
}
});
</script>
运行代码后,单击 “点我发送异步请求” 按钮,查看前端控制台,如图 12-18 所示。从图中可知,已成功获取 List 集合中的 User 对象属性值,另外查看后端控制台,可以看到传递回前端的 List 集合的类型为 JSON 字符串格式,如图 12-19 所示。

图 12-19 Lsit 集合转换为 JSON 字符串格式
| Output | |
| ↑ | 访问到了JsonTestServlet |
| >> | s = [{"id":1,"username":"admin1","password":"root"},{"id":2,"username":"admin2","password":"root"},{"id":3,"username":"admin3","password":"root"}] |
12.7.3 Map 集合作为响应数据¶
当 Map 集合作为响应数据时,同样以包含多个 User 对象的集合为例进行演示。
修改 JsonTestServlet,将包含多个 User 对象的 Map 集合转换为 JSON 字符串格式,并响应数据到前端页面,示例代码如下。
package com.atguigu.servlet;
//省略 import 语句
@WebServlet("/jsonTest")
public class JsonTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问到了 JsonTestServlet");
//创建 User 对象
User user = new User(1, "admin", "root");
//将 Map 集合作为响应数据
Map<String, User> map = new HashMap<>();
map.put("a01", new User(1, "admin1", "root"));
map.put("a02", new User(2, "admin2", "root"));
map.put("a03", new User(3, "admin3", "root"));
//借助 Gson 对象将 Map 集合转换为 JSON 字符串格式
String s = new Gson().toJson(map);
System.out.println("s = " + s); // {a01: {}, a02: {}, a03: {} }
response.getWriter().write(s);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
前端 JavaScript 代码如下。
<script>
new Vue({
el:"#app",
data: {},
methods: {
fun02:function () {
//采用 Axios 发送异步请求
axios({
method:"post",//设置请求方式
url:"jsonTest",//设置请求路径
//设置请求参数
params: {}
}).then(response => {
//Map
var mapObj = response.data;
console.log(mapObj.a01.id);
console.log(mapObj.a01.username);
console.log(mapObj.a02.id);
console.log(mapObj.a02.username);
console.log(mapObj.a03.id);
console.log(mapObj.a03.username);
});
}
}
});
</script>
运行代码后,单击按钮,查看前端控制台,结果与 List 集合的遍历结果相同。再次查看后端控制台,发现 Map 集合已转换为如下 JSON 字符串格式,如图 12-20 所示。
图 12-20 Map 集合转换为 JSON 字符串格式
| 访问到了JsonTestServlet | |
| s = {"a02":{"id":2,"username":"admin2","password":"root"},{"a01":{"id":1,"username":"admin1","password":"root"},{"a03":{"id":3,"username":"admin3","password":"root"}) |
12.8 统一响应数据模板¶
在实际开发中,为了方便团队协作开发,提高开发效率,建议前后端交互时使用统一的数据格式。凡是涉及给 AJAX 请求返回响应数据的情况,都可以封装到 CommonResult 类中实现。CommonResult 类的三个属性如表 12-1 所示。
表 12-1 CommonResult 类的三个属性
| 属性名 | 含义 |
| flag | 服务器端处理请求的结果,取值为 true 或者 false |
| resultData | 服务器端处理请求成功之后,需要响应给客户端的数据 |
| message | 服务器端处理请求失败之后,需要响应给客户端的数据 |
一般返回数据时,包括两种组合方式,如果响应正常,就返回 flag 值为 true 和 resultData 响应的数据;如果响应失败,就返回 flag 值为 flase 和 message 提示信息。
package com.atguigu.bean;
//目的是为了统一异步响应数据的模板(方便团队开发使用)
public class CommonResult {
//服务器端处理请求的标示,用于设置响应是否正常,true 表示正常,flase 表示不正常
private boolean flag;
//当服务器端处理请求成功时,要显示给客户端的数据(主要针对于查询)
private Object resultSetData;
//当服务器端处理请求失败时,要响应给客户端的错误信息
private String message;
//处理请求成功
public static CommonResult ok() {
return new CommonResult().setFlag(true);
}
//处理请求失败
public static CommonResult error() {
return new CommonResult().setFlag(false);
}
public boolean isFlag() {
return flag;
}
private CommonResult setFlag(boolean flag) {
this.flag = flag;
return this;
}
public Object getResultData() {
return resultData;
}
public CommonResultях (Object工作报告) {
this.resultData = resultData;
return this;
}
public String getMessage() {
return message;
}
public CommonResult statements(String message) {
this.message = message;
return this;
}
@Override
public String toString() {
return "CommonResult{ " +
"flag=" + flag +
例如,借助统一响应数据模板 CommonResult 向前端页面响应普通字符串和 JavaBean 数据,示例代码如下。
//1.返回普通的字符串
CommonResult commonResult = CommonResult.ok().setResultData("success");
//commonResult: flag=true, resultData="success", message=null
String s = new Gson().toJson(commonResult);
System.out.println("返回普通字符串:");
System.out.println("s = " + s);
//2.返回 JavaBean 数据
User user1 = new User(2, "atguigu", "12345");
CommonResult commonResult1 = CommonResult.ok().setResultData(user1);
//commonResult1: flag=true, resultData={id:2, username:atguigu, password:12345}
String s1 = new Gson().toJson(commonResult1);
System.out.println("返回 JavaBean:");
System.out.println("s1 = " + s1);
运行代码后,查看后端控制台,如图 12-21 所示。
图 12-21 借助模板返回数据
| Output | |
| + | 访问到了JsonTestServlet |
| ↓ | 返回普通字符串: |
| → | s = {"flag":true,"resultData":"success"} |
| → | 返回JavaBean: |
| ← | s1 = {"flag":true,"resultData":{"id":2,"username":"atguigu","password":"12345"}} |
12.9 案例:用户名重复验证¶
下面借助异步请求,处理完善 chapter09_login_register 项目的注册功能,使用 Axios 完成用户名重复验证功能。
创建 chapter12_login_register 模块,并复制 chapter09_login_register 用户登录注册项目,在此基础上实现以下代码。
修改 register.html 文件,在输入手机号码时绑定失去焦点的事件,校验是否重复注册。
<p class="clearfix">
<label class="one" for="agent">手机号码:</label>
<input id="agent" name="phone" v-model="phone"
@blur="checkPhone" type="text" class="required" maxlength="11"
placeholder="请输入您的手机号码"/>
</p>
<p class="clearfix">
<span style="color: red;margin-left: 90px;">{{phoneMsg}}</span>
</p>
发送异步请求,携带手机号码参数到后台进行处理,并针对响应结果展示不同的提示信息,如果该手机号码未注册,就提示手机号可用;如果该手机号已注册,就提示该手机号已注册,请更换。
<script>
new Vue({
el:"#register",
data: {
phone: "",
phoneMsg: ""
},
methods: {
checkPhone() {
axios({
method:"post",
url:"checkPhone",
params: {
phone:this.phone
}
}).then(response => {
if (response.data=="ok") {
this.phoneMsg="手机号码可用"
}else{
this.phoneMsg="该号码已注册,请更换"
}
})
}
}
}
})
</script>
修改 Service 层,添加校验输入手机号码是否在数据库中已存在的方法,UserServiceImpl 示例代码如下。
package com.atguigu.service.impl;
// 省略 import 语句
public class UserServiceImpl implements UserService {
privateilikUserDao userDao=new UserDaoImpl();
//根据手机号码查询用户信息
@Override
public User checkUsername(String username) {
return userDao.findUserByPhone(username);
}
}
创建 ServletCheckPhone,处理异步请求,示例代码如下。
package com.atguigu.servlet.model;
//省略 import 语句
@Servlet("/checkPhone")
public class ServletCheckPhone extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取手机号码
String phone = request.getParameter("phone");
//校验手机号码是否存在
UserService userService=new UserServiceImpl();
User user = userService.checkUsername(phone);
//给响应
if(user==null){
response.getWriter().write("ok");
} else{
response.getWriter().write("no");
}
}
启动项目,打开注册页面,如图 12-22 所示,输入已经注册过的手机号码,显示 “该号码已注册,请更换”。

然后输入新号码,显示 “手机号码可用”,如图 12-23 所示。从图中可知,成功实现用户名重复验证功能。

12.10 本章小结¶
本章主要介绍了 AJAX 异步请求,介绍了 AJAX 渲染数据的特点、同步与异步的区别,以及 Axios 框架的基本使用等。同时,通过代码详细介绍了 AJAX 如何响应 JavaBean、List 集合和 Map 集合等复杂数据。最后将响应数据整个过程的代码封装成模板,统一前后端交互时使用的数据格式,方便实际开发中的团队协作,提高开发效率。通过本章学习,希望大家了解 AJAX 异步请求和渲染数据的过程,掌握 Axios 框架的使用方式,能够编写代码实现向前端页面响应数据。