目录
AJAX
概念
工作原理
使用套路
创建XMLHttpRequest
指定请求头
发送请求
接收服务器响应
示范用例
案例
处理POST请求(注册)
返回XML
省市联动
XStream
JSON
Ajax小工具
后话
AJAX
概念
AJAX全称Asynchronous JavaScript And XML,译为“异步JavaScript和XML”。即通过JavaScript与服务器进行异步交互,对网页的某部分进行更新。
那么何为异步交互呢?异步交互即当发送一个请求时,无需等待服务器的响应即可发第二个请求。通过这样的现象可以使用js接收服务器的响应,然后利用js来局部刷新页面中的某一部分内容。而同步交互则是必须得等待第一个请求响应结束后,才能发出第二个请求。
工作原理
上一个图,源自RUNOOB。
需要关注的是第一个方框中的XMLHttpRequest对象,这个对象是Ajax核心,用于在后台与服务器交换数据。可以说掌握了它就掌握了Ajax。创建XMLHttpRequest对象后,随后用其打开与服务器的连接,将请求发送至服务器。服务器处理完请求后将响应返回浏览器后,XMLHttpRequest对象就会获取到响应来完成操作。
使用套路
创建XMLHttpRequest
在创建之前,需要知道现代浏览器(IE7+、Chrome、FireFox、Safari以及Opera)都支持XMLHttpRequest对象,只有IE5、IE6使用ActiveX对象。为了对所有浏览器支持,需要检查浏览器是否支持XMLHttpRequest对象,即代码如下所示:
function createXMLHttpRequest() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
} else {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
指定请求头
查看API,在XMLHttpRequest中有一个方法用于指定请求的内容,如下所示:
open(method, url, async)
其中,method表示请求方式,如GET、POST、DELETE请求等;url表示要访问的位置,即指定服务器端资源;async表示是否异步处理请求,当为true时为异步处理请求,为false时为同步处理请求。
发送请求
当设置完请求内容后,需要将请求发送至服务器,而在XMLHttpRequest中有很多方法来处理不同数据形式,如下所示:
send(data : Document)
send(data : FormData)
send(data : Blob)
send(data : ArrayBuffer)
send(data : String)
方法中的参数其实就是请求体的内容,当请求方式为get时需要设置参数为null,以防部分浏览器无法发送。
接收服务器响应
当请求发送至服务器端后,服务器端将请求处理完毕把响应发送给浏览器。在XMLHttpRequest中有一个onreadystatechange事件,它会在XMLHttpRequest对象的状态发生变化时被调用。在说这个事件之前,需要知道XMLHttpRequest中有五个状态,如下所示:
0:初始化未完成状态,即创建了对象但未调用open()方法
1:请求已开始状态,即调用了open()方法但未调用send()方法
2:请求发送完成状态,即调用了send()方法
3:开始读取服务器响应
4:读取服务器响应结束
那么到底如何得到XMLHttpRequest对象的状态、服务器响应的状态码以及响应的内容的?这些在XMLHttpRequest对象中都有相对应的实例属性来处理,即如下所示:
XMLHttpRequest xmlHttp = new XMLHttpRequest();
// 得到XMLHttpRequest对象的状态
var state = xmlHttp.readyState;
// 得到服务器响应的状态码
var status = xmlHttp.status;
// 得到服务器响应的内容
var content = xmlHttp.responseText;
var content = xmlHttp.responseXML;
需要注意的是,responseText和responseXML得到的内容是不同类的,前者是字符串String,后者是Document对象。
示范用例
根据这四个套路,先创建一个页面,如下所示:
<head>
<script type="text/javascript">
function createXMLHttpRequest() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
window.onload = function() {
var btn = document.getElementById("btn");
btn.onclick = function() {
// 1.得到异步对象
var xmlHttp = createXMLHttpRequest();
// 2.设置请求头
xmlHttp.open("GET", "<c:url value='/AjaxServlet1'/>", true);
// 3.发送请求
xmlHttp.send(null);
// 4.注册监听事件获得响应
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
// 获取服务器的响应内容
var text = xmlHttp.responseText;
var context = document.getElementById("content");
content.innerHTML = text;
}
};
};
};
</script>
</head>
<body>
<button id="btn">click here</button>
<h2 id="content"></h2>
</body>
接着写一个Servlet
public class AjaxServlet1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
response.getWriter().print("Hello Ajax!");
}
}
结果如下所示:
JS中是可以使用Jstl标签库的,原因是在于页面由服务器发送,在发送之前会将jsp进行编译,而产生的html格式页面则将JS其中的标签变成了普通字符串。
案例
处理POST请求(注册)
以注册为例,表单需要对输入的用户名进行验证,以防同名用户出现。在XMLHttpRequest中的send()方法可以传表单项参数亦可传递整个表单的表单项集合。为了达到传递整个表单的表单项集合,决定使用FormData对象。
FormData对象是将表单项的key以及value形成键值对以序列化的形式用于方便传递至服务器。而使用了FormData对象等同于setRequestHeader("Content-Type", "multipart/form-data"),即常规的request.getParameter(String name)方法不可再使用,这里就得使用回FileUpload包。
明确了这两点后,先创建一个页面,其如下所示:
<form id='test-form' method="POST" >
用户名: <input type="text" name='username' id="usernameEle"><span id="errorSpan"></span><br>
密码: <input type="text" name='password'><br>
<input type='submit' value="注册">
</form>
<h2 id="content"></h2>
获取表单元素对其提交事件进行注册,如下所示:
window.onload = function() {
var form = document.getElementById('test-form');
form.onsubmit = function(event) {
event.preventDefault();
// 1.得到异步对象
var xmlHttp = createXMLHttpRequest();
// 2.设置请求头
xmlHttp.open("POST", "<c:url value='/AjaxServlet1'/>", true);
// 3.发送请求
var formData = new FormData(form);
xmlHttp.send(formData);
// 4.注册监听事件获得响应
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
// 获取服务器的响应内容
var text = xmlHttp.responseText;
var context = document.getElementById("content");
content.innerHTML = text;
}
};
};
// ...其他代码...
}
Servlet获取表单请求参数,如下所示:
public class AjaxServlet1 extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String str = "";
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(factory);
try {
List<FileItem> fileItems = sfu.parseRequest(request);
for(FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
str += fileItem.getString("UTF-8") + " ";
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
response.getWriter().print("Hello Ajax!" + str);
}
}
接下来简单演示用户名的表单项验证。当用户在输入框中用户名时向服务器端请求查询,当用户名等于“张三”时返回“1”,反之则返回“0”。接着通过异步对象获取服务器的响应内容,当响应内容为“1”时显示红色的“用户名已注册”的提示,为“0”时显示绿色的“用户名可使用”的提示。
因此创建一个名为ValidateUsernameServlet,代码如下所示:
public class ValidateUsernameServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
if (request.getParameter("username").equals("张三")) {
response.getWriter().print("1");
} else {
response.getWriter().print("0");
}
}
}
在window.onload里加入以下代码:
var userEle = document.getElementById("usernameEle");
userEle.onblur = function () {
// 1.得到异步对象
var xmlHttp = createXMLHttpRequest();
// 2.设置请求头
xmlHttp.open("POST", "<c:url value='/ValidateUsernameServlet'/>", true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 3.发送请求
xmlHttp.send("username=" + userEle.value);
// 4.注册监听事件获得响应
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
// 获取服务器的响应内容
var text = xmlHttp.responseText;
var errorSpan = document.getElementById("errorSpan");
if (text == "1") {
errorSpan.style.color = 'red';
errorSpan.innerHTML = "用户名已被注册";
} else {
errorSpan.style.color = 'green';
errorSpan.innerHTML = "用户名可使用";
}
}
};
};
显示结果如下:
返回XML
上面说过,XMLHttpRequest可以返回两种响应内容,其中一种是文本,另一种是XML。写一个Servlet如下所示(为了方便演示直接在Servlet中定义了一个xml样板):
public class AjaxServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
String xml = "<users>" +
"<user id = '0001'>" +
"<username>zhangsan</username>" +
"<password>123456</password>" +
"</user>" +
"</users>";
response.setContentType("text/xml;charset=utf-8");
response.getWriter().print(xml);
}
}
页面的JS代码如下所示:
window.onload = function() {
var btn = document.getElementById("btn");
btn.onclick = function() {
// 1.得到异步对象
var xmlHttp = createXMLHttpRequest();
// 2.设置请求头
xmlHttp.open("GET", "<c:url value='/AjaxServlet2'/>", true);
// 3.发送请求
xmlHttp.send(null);
// 4.注册监听事件获得响应
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
// 获取服务器的响应内容
var doc = xmlHttp.responseXML;
var ele = doc.getElementsByTagName('user')[0];
var id = ele.getAttribute('id');
var username;
if(window.addEventListener) {
username = doc.getElementsByTagName('username')[0].textContent;
} else {
username = doc.getElementsByTagName('username')[0].text;// IE
}
var password;
if(window.addEventListener) {
password = doc.getElementsByTagName('password')[0].textContent;
} else {
password = doc.getElementsByTagName('password')[0].text;// IE
}
var text = id + "," + username + "," + password;
document.getElementById('content').innerHTML = text;
}
};
};
};
结果如下所示:
省市联动
所谓的“省市联动”即两个下拉框,通过选择省份的下拉框,动态显示市级的选项。页面如下所示:
<select name="province" id="province">
<option>===请选择省===</option>
</select>
<select name="city" id="city">
<option>===请选择市===</option>
</select>
在这里用xml文件来演示,即如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<china>
<province name="广东">
<city>广州</city>
<city>深圳</city>
<city>韶关</city>
<city>珠海</city>
<city>佛山</city>
<city>湛江</city>
</province>
<province name="四川">
<city>成都</city>
<city>自贡</city>
<city>攀枝花</city>
<city>德阳</city>
<city>乐山市</city>
</province>
</china>
需要实现的效果:当页面加载完毕后,向服务器端发送请求,得到所有省份的名称,显示在下拉框中。Servlet代码如下所示:
public class ProvinceServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
// 解析XML得到Document对象
SAXReader reader = new SAXReader();
InputStream input = this.getClass().getResourceAsStream("/china.xml");
Document doc = reader.read(input);
/*
* 查询所有province的name属性,得到一堆的属性对象
* 循环遍历,把所有的属性值连接成一个字符串,发送给客户端
* */
List<Attribute> attrList = doc.selectNodes("//province/@name");
StringBuilder sb = new StringBuilder();
for(int i = 0; i <attrList.size(); i++) {
sb.append(attrList.get(i).getValue());
if (i < attrList.size() - 1)
sb.append(",");
}
response.getWriter().print(sb);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在window.onload添加以下代码:
window.onload = function() {
var xmlHttp = createXMLHttpRequest();
xmlHttp.open("GET", "<c:url value='/ProvinceServlet' />", true);
xmlHttp.send();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
var text = xmlHttp.responseText;
var arr = text.split(",");
for(var i = 0; i < arr.length; i++) {
var option = document.createElement("option");
option.value = arr[i];
var textNode = document.createTextNode(arr[i]);
option.appendChild(textNode);
document.getElementById("province").appendChild(option);
}
}
};
};
访问页面,可以发现在省份的下拉列表中已经可以看到两个省。接下来完成当选取其中一个省份时,在市的下拉框显示对应的所属省份的城市。需要用到的Servlet代码如下所示:
public class CityServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("UTF-8");
// 需要注意返回的是xml
response.setContentType("text/xml;charset=utf-8");
SAXReader reader = new SAXReader();
InputStream input = this.getClass().getResourceAsStream("/china.xml");
Document document = reader.read(input);
String pname = request.getParameter("pname");
Element provinceEle = (Element) document.selectSingleNode("//province[@name='" + pname + "']");
String xmlStr = provinceEle.asXML();
response.getWriter().print(xmlStr);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
向省份的下拉列表添加改变监听,如下所示:
var proSelect = document.getElementById("province");
proSelect.onchange = function () {
var xmlHttp = createXMLHttpRequest();
xmlHttp.open("POST", "<c:url value='/CityServlet' />", true);
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttp.send("pname=" + proSelect.value);
xmlHttp.onreadystatechange = function () {
var citySelect = document.getElementById("city");
var optionList = citySelect.getElementsByTagName("option");
while (optionList.length > 1)
citySelect.removeChild(optionList[1]);
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
var doc = xmlHttp.responseXML;
var cityList = doc.getElementsByTagName("city");
for(var i = 0; i < cityList.length; i++) {
var cityEle = cityList[i];
var cityName = cityEle.innerHTML;
var option = document.createElement("option");
option.value = cityName;
var textNode = document.createTextNode(cityName);
option.appendChild(textNode);
citySelect.appendChild(option);
}
}
};
};
访问页面,结果如下所示:
XStream
Xstream是一个功能比较强大的xml和java对象互转的工具包。在使用XStream之前需要导入两个包:一个是核心包xstream-1.4.7.jar,一个是依赖包xpp3_min-1.1.4c(XML Pull Parser)。
使用方法如下:
XStream xstream = new XStream();
String xmlStr = xstream.toXML(javabean);
类中包含的方法如下所示:
// 别名:即将类型对应的元素名修改成参数name
public void alias(String name, Class type);
// 将类的成员生成为元素的属性
public void useAttributeFor(Class definedIn, String fieldName);
// 去除集合类型的成员,只保留内容
public void addImplicitCollection(Class ownerType, String fieldName);
// 去除类的指定成员的名称,即不生成对应的xml元素
public void omitField(Class definedIn, String fieldName);
分别创建一个Province以及City类,代码如下所示:
public class Province {
private String name;
private List<City> cities = new ArrayList<City>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<City> getCities() {
return cities;
}
public void setCities(List<City> cities) {
this.cities = cities;
}
public void addCity(City city) {
cities.add(city);
}
}
public class City {
private String name;
private String description;
public City(String name, String description) {
super();
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
接下来开始演示,代码如下所示:
public List<Province> getProvinceList() {
Province p1 = new Province();
p1.setName("广东");
p1.addCity(new City("深圳", "ShenZhen"));
p1.addCity(new City("广州", "GuangZhou"));
Province p2 = new Province();
p2.setName("四川");
p2.addCity(new City("成都", "ChengDu"));
p2.addCity(new City("德阳", "DeYang"));
List<Province> provinceList = new ArrayList<Province>();
provinceList.add(p1);
provinceList.add(p2);
return provinceList;
}
@Test
public void func3() {
List<Province> proList = getProvinceList();
XStream xstream = new XStream();
xstream.alias("china", List.class);
xstream.alias("province", Province.class);
xstream.alias("city", City.class);
// 把Province类的name属性生成<province>元素的属性
xstream.useAttributeFor(Province.class, "name");
// 去除Collection类型的属性,即cities
xstream.addImplicitCollection(Province.class, "cities");
// 让City类的description属性不生成对应的xml元素
xstream.omitField(City.class, "description");
System.out.println(xstream.toXML(proList));
}
结果如下所示:
<china>
<province name="广东">
<city>
<name>深圳</name>
</city>
<city>
<name>广州</name>
</city>
</province>
<province name="四川">
<city>
<name>成都</name>
</city>
<city>
<name>德阳</name>
</city>
</province>
</china>
JSON
JSON是JS提供的一种数据交换格式。所谓的JSON实际上是一个字符串表达形式,在这里稍微提及一下JSON的语法,对象的属性名必须用双引号,属性值可以是为null、数值、字符串、数组、布尔值。
之所以在这里提到JSON,主要是为了说一个方便的小工具json-lib,其可以使用Java语言来创建JSON字符串,亦可把JavaBean转换成JSON。
json-lib小工具所需要的包比XStream的包还多,其中json-lib.jar为核心包。
小工具里使用JSONObject来表示JSON,实际上是一个实现了JSON、Map、Comparable接口的一个类。。
接下来演示一下小工具的使用,演示代码如下所示:
public class Demo01 {
public void func1() {
JSONObject map = new JSONObject();
map.put("name", "zhangsan");
map.put("age", 23);
map.put("sex", "male");
String s = map.toString();
System.out.println(s);
}
public void func2() {
Student stu = new Student("zhangsan", 20, "male");
JSONObject map = JSONObject.fromObject(stu);
System.out.println(map.toString());
}
public void func3() {
Student stu = new Student("zhangsan", 20, "male");
Student stu2 = new Student("lisi", 18, "female");
JSONArray list = new JSONArray();
list.add(stu);
list.add(stu2);
System.out.println(list.toString());
}
@Test
public void func4() {
Student stu = new Student("zhangsan", 20, "male");
Student stu2 = new Student("lisi", 18, "female");
List<Student> list = new ArrayList<Student>();
list.add(stu);
list.add(stu2);
System.out.println(JSONArray.fromObject(list).toString());
}
}
所用的JavaBean对象如下所示:
public class Student {
private String name;
private int age;
private String sex;
public Student(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
对于这个小工具只要会使用即可,若是想研究其内部机理,可以查看API以及源代码。
Ajax小工具
在先前学案例的时候会发现许多代码都是重复的,将重复代码部分整理,得到如下工具:
function createXMLHttpRequest() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
// method/*请求方式*/,
// url/*请求的url*/,
// asyn/*是否异步*/,
// params/*请求体*/,
// callback/*回调方法*/,
// type/*服务器响应数据转换成什么类型*/
function ajax(option) {
// 得到xmlHttp
var xmlHttp = createXMLHttpRequest();
if(!option.method)
option.method = "GET";
if(option.asyn == undefined)
option.asyn = true;
// 打开连接
xmlHttp.open(option.method, option.url, option.asyn);
if (option.method == "POST") {
xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
// 发送请求
xmlHttp.send(option.params);
// 注册监听
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
var data;
if (!option.type) {
data = xmlHttp.responseText;
} else if (option.type == "xml") {
data = xmlHttp.responseXML;
} else if (option.type == "text") {
data = xmlHttp.responseText;
} else if (option.type == "json") {
var text = xmlHttp.responseText;
data = eval("(" + text + ")");
}
callback(data);
}
}
}
后话
'''
若是未学习过JS以及JQuery,这一个小工具可以方便理解JQuery中的$.ajax()的构造
'''