JavaWeb_09_Ajax


在数据库CRUD这个例子中,可以看到,浏览器端展示的页面,在每次操作之后都是完全由服务器返回的页面,即页面是全部刷新的,称为全局刷新。但是有时候,我们进行一步操作之后,页面展示的内容并没有完全发生变化,即只有一小部分页面的内容发生了变化,那么这个时候如果还是采用全局刷新的形式,就会有很多无用的数据传输,响应时间变长,即浪费了带宽资源,也影响了用户体验。所以可以采用局部刷新(AJAX, Asynchronous JavaScript and XML),只传输需要改变的内容数据。

全局刷新的流程如下所示:

ajax_001.png (867×208) (gitee.io)

ajax_002.png (857×197) (gitee.io)

异步刷新的流程如下所示:

ajax_003.png (856×372) (gitee.io)

1. 异步请求对象

在全局刷新中,浏览器向服务器发起请求,服务器响应内容,浏览器根据响应内容渲染页面。那么在局部刷新中,由异步对象(XMLHttpRequest)代替浏览器发起请求,并获取数据(服务端只会原路返回响应数据,所以谁发送的数据,谁就会收到数据),将其展示在浏览器页面上。在浏览器内存中,可以创建多个异步对象,每个都可独立地发起请求。

XMLHttpRequest对象能够:

  • 在不重新加载页面的情况下更新网页;
  • 在页面已加载后向服务器请求数据;
  • 在页面已加载后从服务器接收数据。

AJAX, 即Asynchronous JavaScript and XML,和同步相对应,异步的JavaScript和xml;AJAX并不是一种语言,是一种方法。本质上AJAX属于JavaScript的内容,异步刷新对象可由JS来创建,如下所示:

1
var xmlhttp = new XMLHttpRequest(); 

JavaScript负责创建异步对象,发送请求,更新页面的dom对象。XML用于网络中传输数据的格式(现在一般用json格式来传输数据)。

异步刷新的步骤如下所示:

  1. 创建异步对象;

    1
    var xmlhttp = new XMLHttpRequest(); 
  2. 给异步对象绑定事件(onreadystatechange

    当异步对象发起请求或者获取数据,都会触发这个事件(实际上异步对象有一个属性,该属性readyState会随着请求对象的状态变化而变化,该属性的变化对应这个事件)。我们可以指定一个函数来处理相关操作(如刷新页面)。

    1
    2
    3
    xmlHttp.onreadystatechange = function(){
    处理请求的状态变化。
    }

    readyState属性代表异步对象XMLHttpRequest的状态,共5个(0,4):

    • 0,请求未初始化,即刚刚创建完异步请求对象,var xmlhttp = new XMLHttpRequest();
    • 1,初始化异步请求对象,xmlhttp.open(请求方法,请求地址,true)
    • 2,异步对象发送请求,xmlhttp.send()
    • 3,异步对象接收应答数据,即从服务端返回数据,XMLHttpRequest内部处理。
    • 4,异步请求对象已经将数据解析完毕,此时才可以读取数据。开发人员根据数据来更新当前页面。所以我们主要判断异步对象的该属性是否为此状态。

    除此之外,因为是网络请求,所以还需要异步请求对象的另一个属性status,表示网络响应的状态码。常见的状态码如下所示:

    • 200,请求成功
    • 404,资源未找到
    • 405,服务器不处理请求中的方法(如post、get等等)
    • 500,服务器内部错误

    所以,当status为200,并且readyState为4时,表示异步对象成功获得了请求成功并响应的数据,程序员可以进一步操作

  3. 初始化异步请求对象的参数

    根据异步对象的方法open()初始化异步请求对象的参数,即xmlHttp.open(请求方法get|post,请求地址,异步true(默认)|同步请求false)

  4. 异步对象发送请求

    根据异步对象的方法xmlHttp.send()方法发送请求。发送请求后,等待服务器端响应。那么怎么获取服务器端的数据呢?

    利用上述第2步的readyState和status两个属性判断,判断成功后,利用异步请求对象的responseText属性获取数据。

2. 异步刷新案例

简单的一个Servlet案例,计算BMI值,由前端提交数据,后端计算好BMI后返回给前端。

2.1 首先前端采用jsp来做,异步刷新采用4步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>局部刷新</title>
<script type="text/javascript">

function doAjax() {
// 1. 创建异步请求对象
var xmlHttp = new XMLHttpRequest();

// 2. 绑定事件
xmlHttp.onreadystatechange = function(){
// 事件发生后处理服务器端返回的数据,更新当前页面
// 可以先进行一下状态的测试
alert("readyState:" + xmlHttp.readyState);
};

// 3. 初始化异步请求对象
xmlHttp.open("post", "/Ajax01/asyn", true);

// 4. 异步请求对象发送请求
xmlHttp.send();
}

</script>
</head>
<body>
<!-- 注意,Ajax不局限于form表单提交,采用异步对象发送请求-->
姓名:<input type="text" name="name" id="name"><br>
身高(m):<input type="text" name="height" id="height"><br>
体重(kg):<input type="text" name="weight" id="weight"><br>
<input type="button" value="计算" onclick="doAjax()">
</body>
</html>

可以先在第2步测试一下异步对象的状态变化。

首先是初始化异步对象(状态为1,状态0是取不到的)。

ajax_004.png (1356×770) (gitee.io)

点击确定按钮后,然后是异步对象发送请求(状态为2)。注意,下面的type为xhr指的就是XML Http Request的简称。可以看到发生了网络数据的传输

ajax_005.png (1345×768) (gitee.io)

之后,点击确定按钮,状态为4。(因为服务器端没有返回数据,所以这里没有状态3)

ajax_006.png (1346×774) (gitee.io)

服务端返回数据,利用status和readyState来获取数据并显示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script type="text/javascript">

function doAjax() {
// 1. 创建异步请求对象
var xmlHttp = new XMLHttpRequest();

// 2. 绑定事件
xmlHttp.onreadystatechange = function(){
// 事件发生后处理服务器端返回的数据,更新当前页面
if(xmlHttp.status == 200 && xmlHttp.readyState == 4){
alert("数据:" + xmlHttp.responseText);
}

};

// 3. 初始化异步请求对象
xmlHttp.open("post", "/Ajax01/asyn", true);

// 4. 异步请求对象发送请求
xmlHttp.send();
}
</script>

如下所示,可以看到即使收到服务器返回的数据,浏览器端的页面并没有变化。

ajax_007.png (1215×770) (gitee.io)

可以看到,xmlHttp.responseText实际上就是Servlet中输出流输出的内容。经过上述测试,完整的jsp页面如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>局部刷新</title>
<script type="text/javascript">

function doAjax() {
// 1. 创建异步请求对象
var xmlHttp = new XMLHttpRequest();

// 2. 绑定事件
xmlHttp.onreadystatechange = function(){
// 事件发生后处理服务器端返回的数据,更新当前页面
if(xmlHttp.status == 200 && xmlHttp.readyState == 4){
// alert("数据:" + xmlHttp.responseText);
// 接收数据并更新局部页面
document.getElementById("data").innerText = xmlHttp.responseText;
}
};

// 3. 初始化异步请求对象
// 首先获取要传送的数据
var height = document.getElementById("height").value;
var weight = document.getElementById("weight").value;

var param = "height=" + height + "&weight=" + weight;

xmlHttp.open("post", "/Ajax01/asyn", true);

// (open之后)采用post方式需写下面一行(即告诉服务器本请求是post请求)
xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

// 4. 异步请求对象发送请求
xmlHttp.send(param);
}

</script>
</head>
<body>
<!-- 注意,Ajax不局限于form表单提交,采用异步对象发送请求-->
姓名:<input type="text" name="name" id="name"><br>
身高(m):<input type="text" name="height" id="height"><br>
体重(kg):<input type="text" name="weight" id="weight"><br>
<input type="button" value="计算" onclick="doAjax()"><br>
<br>
<div id="data">等待服务器计算并返回数据...</div>
</body>
</html>

2.2 服务器采用Servlet处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AsynServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

request.setCharacterEncoding("UTF-8");

String height = request.getParameter("height");
String weight = request.getParameter("weight");

double bmi = Double.parseDouble(weight) / Math.pow(Double.parseDouble(height), 2);

System.out.println(bmi);

response.setContentType("text/html; charset=UTF-8");

PrintWriter out = response.getWriter();
out.write(String.valueOf(bmi));
}
}

备注,其实send方法的参数就是请求体中的内容,如果采用get请求方式,则不需要参数;如果采用post请求方式,则需要设置传递的参数。

ajax_008.png (1125×888) (gitee.io)

2.3 总结

本质上利用异步请求对象,然后根据请求方式的不同,构建URL即可。服务器端Servlet没什么区别。利用异步对象的readyState和status两个属性来判断是否正常请求和响应,然后利用异步对象的responseText属性来获取数据,进而根据id来局部更新页面的内容。

异步对象为什么称为异步呢?当其发起请求后,不用等待数据处理完毕,就可以执行其他的操作,等上面的处理完毕后再回来继续执行操作。相当于多线程,不同的线程之间互不影响。

3. json

上面的例子中,服务端仅仅是返回的简单数据,如果返回的是比较复杂的数据,比如性别、年龄、身高、体重等等这些很多个字段的数据该怎么办呢?必然是将其封装成对象,但是这些数据前端需要拿到,所以封装的对象,前后端要都有这种类型对象,可以采用json来做(但是网络传输,在另一端一定是字符串形式,即json形式的数据转换成的字符串,在另一端还要将字符串转换成json对象)。除此之外,json还有以下特点:

  1. json格式好理解;
  2. json格式数据在多种语言中,比较容易处理,使用Java、JavaScript读写json格式的数据比较容易;
  3. json格式数据在网络中传输较快。

常用的json工具包有Gson、FastJson、Jackson、Json-lib。这些库有的依赖较多、性能差,有的性能较好,有的规范不是很全面。介绍一个常用的Jackson工具库,Json工具库和JDBC一样,属于标准,需要第三方实现,这里采用Jackson工具包,将所需要的三个jar包导入到工程lib中即可。

ajax_009.png (438×80) (gitee.io)

**注意,如果服务器端响应的数据为json格式数据的话,需要设置响应内容类型response.setContentType("application/json;charset=utf-8")**。

3.1 案例

服务器端返回json格式的数据(字符串),浏览器接收到后(字符串形式),将其转换成json格式数据读取并显示。首先简单测试一下Json工具库,该库中有一个ObjectMapper类,该类有一个writeValueAsString方法,将对象映射为Json格式的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JsonTest01 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

User u = new User("张三", 23, 1.75, 68);

System.out.println("采用toString方法输出对象:");
System.out.println(u);

ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(u);
System.out.println("采用映射类将对象转换成json类型字符串:");
System.out.println(json);
}
}

ajax_010.png (632×141) (gitee.io)

然后,服务端创建json格式类型的字符串,将其响应到浏览器端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JsonTest01 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

User u = new User("张三", 23, 1.75, 68);

ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(u);

// 设置输出流,注意响应内容类型不再是文本或者html,而是application/json。
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.print(json);
}
}

简单的前端页面如下所示(注意,如何将收到的json字符串转换为json对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试Json</title>
</head>
<script type="text/javascript">
function doSubmit() {

// 1. 创建对象
var xmlHttp = new XMLHttpRequest();

// 2. 绑定事件
xmlHttp.onreadystatechange = function () {
if(xmlHttp.readyState == 4 && xmlHttp.status == 200){
var responseText = xmlHttp.responseText;

// 将字符串转换为json对象
var json = eval("(" + responseText + ")");

// 异步刷新页面
document.getElementById("name").value = json.name;
document.getElementById("age").value = json.age;
document.getElementById("height").value = json.height;
document.getElementById("weight").value = json.weight;
}
};

// 3. 初始化请求对象
xmlHttp.open("post", "/Ajax02/json", true);

// 4. 发送请求(为了简单起见,post的请求体中没有发送数据,因为数据是服务器自动生成的,不需要客户端提交数据)
xmlHttp.send();

}
</script>
<body>
<form action="/Ajax02/json" method="post">
<input type="button" value="提交" onclick="doSubmit()"><br>
<table border="1px">
<tr>
<td>姓名</td>
<td>年龄</td>
<td>身高</td>
<td>体重</td>
</tr>
<tr>
<td><input type="text" id="name" readonly></td>
<td><input type="text" id="age" readonly></td>
<td><input type="text" id="height" readonly></td>
<td><input type="text" id="weight" readonly></td>
</tr>
</table>
</form>
</body>
</html>

可以看到浏览器中的响应结果以及网络流json如下所示:

ajax_011.png (952×642) (gitee.io)

4. 总结

AJAX异步刷新本质上属于JS上的内容,通过创建异步对象(区别于浏览器自己发送请求,相当于多线程,不影响浏览器其他组件的运行),独立发送请求并接受数据,然后设置浏览器其他组件的value,从而达到异步刷新。有以下四个步骤:

  1. 创建异步对象
  2. 绑定事件(异步刷新页面部分内容)
  3. 初始化异步对象
  4. 异步对象发送请求

另外,传送的数据有时候比较多,比较复杂,服务器端可以将其转换为Json格式的字符串,然后浏览器端收到数据后,将其转换为json对象,再取值。

5. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录