什么是Ajax
Ajax(Asynchronous JavaScript and XML) 異步JavaScript和XML
Ajax實際上是下面這幾種技術的融合:
- (1)XHTML和CSS的基于標準的表示技術
- (2)DOM進行動態顯示和交互
- (3)XML和XSLT進行數據交換和處理
- (4)XMLHttpRequest進行異步數據檢索
- (5)Javascript將以上技術融合在一起
客戶端與服務器,可以在【不必刷新整個瀏覽器】的情況下,與服務器進行異步通訊的技術
為什么我們需要Ajax?
在我們之前的開發,每當用戶向服務器發送請求,哪怕只是需要更新一點點的局部內容,服務器都會將整個頁面進行刷新。
- 性能會有所降低(一點內容,刷新整個頁面!)
- 用戶的操作頁面會中斷(整個頁面被刷新了)
Ajax就是能夠做到局部刷新!
XMLHttpRequest
XMLHttpRequest對象是Ajax中最重要的一個對象。使用Ajax更多的是編寫客戶端代碼,而不是服務端的代碼。
XMLHttpRequest 工作原理
傳統的web前端與后端的交互中,瀏覽器直接訪問Tomcat的Servlet來獲取數據。Servlet通過轉發把數據發送給瀏覽器。
當我們使用AJAX之后,瀏覽器是先把請求發送到XMLHttpRequest異步對象之中,異步對象對請求進行封裝,然后再與發送給服務器。服務器并不是以轉發的方式響應,而是以流的方式把數據返回給瀏覽器
XMLHttpRequest異步對象會不停監聽服務器狀態的變化,得到服務器返回的數據,就寫到瀏覽器上【因為不是轉發的方式,所以是無刷新就能夠獲取服務器端的數據】
創建XMLHttpRequest對象
要創建XMLHttpRequest對象是要分兩種情況考慮的:
- 在IE6以下的版本
- 在IE6以上的版本以及其他內核的瀏覽器(Mozilla)等
<script type="text/javascript">var httpRequest;if(window.XMLHttpRequest) {//在IE6以上的版本以及其他內核的瀏覽器(Mozilla)等httpRequest = new XMLHttpRequest();}else if(window.ActiveXObject) {//在IE6以下的版本httpRequest = new ActiveXObject();}</script>
了解XMLHttpRequest對象的屬性和方法
方法
- open()(String method,String url,boolean asynch,String username,String password)
- send(content)
- setRequestHeader(String header,String value)
- getAllResponseHeaders()
- getResponseHeader(String header)
- abort()
常用的方法就是黑色粗體的前三個
- open():該方法創建http請求
- 第一個參數是指定提交方式(post、get)
- 第二個參數是指定要提交的地址是哪
- 第三個參數是指定是異步還是同步(true表示異步,false表示同步)
- 第四和第五參數在http認證的時候會用到。是可選的
- setRequestHeader(String header,String value):設置消息頭(使用post方式才會使用到,get方法并不需要調用該方法)
- xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
- send(content):發送請求給服務器
- 如果是get方式,并不需要填寫參數,或填寫null
- 如果是post方式,把要提交的參數寫上去
屬性
- onreadystatechange:請求狀態改變的事件觸發器(readyState變化時會調用此方法),一般用于指定回調函數
- readyState:請求狀態readyState一改變,回調函數被調用,它有5個狀態
- 0:未初始化
- 1:open方法成功調用以后
- 2:服務器已經應答客戶端的請求
- 3:交互中。Http頭信息已經接收,響應數據尚未接收。
- 4:完成。數據接收完成
- responseText:服務器返回的文本內容
- responseXML:服務器返回的兼容DOM的XML內容
- status:服務器返回的狀態碼
- statusText:服務器返回狀態碼的文本信息
上面有兩個地方都提及了回調函數,回調函數是什么??
回調函數就是接收服務器返回的內容!
編寫第一個Ajax程序
檢測用戶輸入的用戶名是否為"zhongfucheng",只要不是zhongfucheng,就可以使用!
html代碼
- 創建的div只要用于顯示服務器返回的數據
- 當用戶點擊按鈕的時候,就觸發事件。
<input type="text" id="username"><input type="button" onclick="checkUsername()" value="檢測用戶名是否合法"><div id="result"></div>
JavaScript代碼
- 創建XMLHttpRequest對象
- 創建http請求
- 把文本框的數據發送給http請求的目標
- 指定回調函數
- 編寫回調函數
- 發送http請求
- 回調函數得到http返回的內容,把內容寫在div上
<script type="text/javascript">var httpRequest;function checkUsername() {if(window.XMLHttpRequest) {//在IE6以上的版本以及其他內核的瀏覽器(Mozilla)等httpRequest = new XMLHttpRequest();}else if(window.ActiveXObject) {//在IE6以下的版本httpRequest = new ActiveXObject();}//創建http請求httpRequest.open("POST", "Servlet1", true);//因為我使用的是post方式,所以需要設置消息頭httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");//指定回調函數httpRequest.onreadystatechange = response22;//得到文本框的數據var name = document.getElementById("username").value;//發送http請求,把要檢測的用戶名傳遞進去httpRequest.send("username=" + name);}function response22() {//判斷請求狀態碼是否是4【數據接收完成】if(httpRequest.readyState==4) {//再判斷狀態碼是否為200【200是成功的】if(httpRequest.status==200) {//得到服務端返回的文本數據var text = httpRequest.responseText;//把服務端返回的數據寫在div上var div = document.getElementById("result");div.innerText = text;}}}</script>
效果
實現了局部更新,不需要刷新整一個頁面
XMLHttpRequest解決中文亂碼
在傳統的Web中我們已經解決過中文亂碼問題了。
- 服務器傳送給瀏覽器數據發生亂碼:response設置編碼的時候和瀏覽器頁面的編碼一致便可以解決
- 瀏覽器傳送給服務器數據發生亂碼:如果是post方式,request設置編碼便可以解決。如果是get方式,Tomcat下,使用ISO8859-1編碼得到原本的二進制數組,再使用UTF-8編碼便可以解決
接下來,要介紹的是:我們可以屏蔽任何瀏覽器和任何服務器的編碼格式,瀏覽器發送給服務器的數據不造成亂碼問題!
具體我們是這樣做的:
- 發送數據給服務器的時候,JavaScript使用兩次EncodeURI()
- 服務器得到數據,使用URLEncode.decode(數據,"utf-8")進行解碼
為啥我能說這種方式屏蔽任何瀏覽器和服務器的編碼格式,都不會亂碼呢??
XMLHttpRequest解決緩存問題
在傳統的Web中我們也解決過緩存的問題,通過設置response的頭信息,返回給瀏覽器就可以實現不緩存頁面了。
但是呢,現在我們使用XMLHttpRequest,拿到的不是全新的頁面,僅僅是服務器端發送過來的數據!!
那我們要怎么解決緩存的問題呢??產生緩存的原因就是:我們請求了同一個地址,做了相同的操作。服務端認為我的操作并沒有什么變化,就直接把緩存的信息給我了。這樣的話,我就不能更換驗證碼圖片了(等等應用)。
我們可以這樣做:
- 在每次請求url中加入一個時間戳參數【每次url就不一樣了】
- 加入時間戳參數到url時,也分兩種情況
- url本身就帶有參數了,也就是說有"?"號了,那么添加時間戳的時候就需要用"&"號
- [x] url沒有參數,直接使用"?"號來添加時間戳
if(url.indexOf("?") >= 0){url = url + "&t=" + (new Date()).valueOf();} else{url = url + "?t=" + (new Date()).valueOf();}
XMLHttpRequest跨域訪問
使用XMLHttpRequest去跨域訪問是會出現錯誤的。
我們要怎么解決呢??這時候就要用代理思想了
- XMLHttpRequest先把請求提交給同域的Servlet處理
- 同域Servlet再將XMLHttpRequest的請求提交給跨域的服務器
- 同域Servlet得到跨域服務器的返回值,再返回給XMLHttpRequest
這個時候,XMLHttpRequest跨域訪問就分兩種(GET和POST)情況了,因為這兩種提交數據的方式是不一樣的!
瀏覽器代碼
- 我們需要在調用open方法之前判斷一下要連接的地址是不是以http開頭的,如果是則認為要訪問的是跨域的資源
- 首先將當前url中的”?”變成”&”,這是因為將要連接的地址改為”Proxy?url=” + url以后,如果原來url地址中有參數的話,新的url地址中就會有兩個“?”這會導致服務器端解析參數錯誤,”url=”之后的內容表示本來要訪問的跨域資源的地址。
GET方式
GET方式是直接把參數的信息都放在url地址上,所以處理起來會相對簡單。
步驟:
- 使用StringBuilder裝載著getParameter("url")【獲取得到地址,呆會要做拼接,所以用StringBuilder】
- 得到其他參數的時候,做URLEncode.encode(),因為我們進入Servlet的時候已經被decode了一次【我們要盡可能保留原始請求】(參照解決中文亂碼)
- **遍歷所有的請求參數,只要名字不是"url",就添加到StringBuilder中【第一個參數為"?",其他的參數為"&"】(http://localhost:8080/url?aa=bb&cc=dd)**
- 創建URL對象,把拼接成的StringBuilder傳遞進去
- 使用BufferReader讀取遠程服務器返回的數據,要指定輸入流編碼格式,否則會亂碼
BufferedReader reader = new BufferedReader(new InputStreamReader(URL對象.openSteam(),"UTF-8"));
- 最后,把遠程服務器讀取到的數據再返回給XMLHttpRequest
POST方式
POST方式把參數的信息都封裝到HTTP請求中,在URL進行連接的時候,需要把數據寫給遠程服務器
步驟:
- 得到url參數,創建StringBuilder
- 得到其他參數的時候,做URLEncode.encode(),因為我們進入Servlet的時候已經被decode了一次【我們要盡可能保留原始請求】(參照解決中文亂碼)
- 遍歷所有的請求參數,只要名字不是"url",就添加到StringBuilder中【第一個參數直接給出,其他的參數為"&"】(aa=bb&cc=dd&ee=ff)
- 創建URL對象,創建URL連接器,允許寫數據到遠程服務器上
URL url = new URL(url);URLConnection connection = url.openConnection;connection.setDoOutPut(true);
- 得到寫數據流
OutputSteamWriter writer = new OutputSteamWriter(conncetion.getOutputSteam)
- 把StringBuilder的數據寫到遠程服務器上,并flush
- 使用BufferReader讀取遠程服務器返回的數據
BufferedReader reader = new BufferedReader(new InputSteamReader(conncetion.inputSteamReader,"UTF-8"));
AJAX二級下拉聯動案例【XML版】
我們在購物的時候,常常需要我們來選擇自己的收貨地址,先選擇省份,再選擇城市...
有沒有發現:當我們選擇完省份的時候,出現的城市全部都是根據省份來給我們選擇的。這是怎么做到的呢???其實就是通過AJAX來完成的。使用AJAX技術讓我們看起來網頁非常“智能”,會根據省份來給出對應的城市信息。
我們這里就不讀取數據庫了,直接在Servlet寫死數據來進行模擬測試。
分析
我們知道AJAX與服務器之間的交互常用的傳輸載體格式有三種:
- HTML
- XML
- JSON
由于省份與城市是有層級關系的,因此我們只能用XML或者JSON。
我們這里首先就用XML來進行,后面會使用JSON,來看看他倆有什么不同的地方。。
前臺分析
當用戶選擇了某個省份之后,就使用AJAX與服務器進行交互,那么在選擇城市的時候就出現對應的城市信息。
- 監聽下拉框值變化事件
- 只要下拉框值變化了,就與服務器進行交互
- 得到服務器返回的值,解析XML
- 使用DOM把數據寫到城市下拉框列表中
后臺分析
- 得到前臺帶過來的數據
- 判斷該數據是什么,返回對應的的XML文件
寫JSP頁面
<%--Created by IntelliJ IDEA.User: ozcDate: 2017/5/17Time: 19:38To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>多級聯動</title>
<script type="text/javascript" src="js/ajax.js"></script>
</head>
<body><%--############前臺頁面###################--%>
<select name="province" id="provinceId">
<option value="-1">請選擇省份</option><option>廣東</option>
<option>湖南</option>
</select>
<select name="city" id="cityId">
<option>請選擇城市</option>
</select><%--############AJAX###################--%><script type="text/javascript">document.getElementById("provinceId").onchange = function () {/**********定位到下拉框,獲取下拉框的值***************/// 獲取選中的下拉框索引值var index = this.selectedIndex;// 得到下拉框的值var province = this.options[index].innerHTML;//下拉框的值要是“請選擇”,那么我們是不會訪問服務器的if ("請選擇省份" != province) {/********由于每次都會自動添加,因此每次在調用的時候清除***********/var citySelect = document.getElementById("cityId");//每次都將option變成長度只有1的citySelect.options.length = 1;/*************ajax代碼*********************///創建AJAX對象var ajax = createAJAX();//準備發送請求var method = "post";var url = "${pageContext.request.contextPath}/ProvinceServlet?time=" + new Date().getTime();ajax.open(method, url);//由于是POST方式,因此要設置頭ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");ajax.send("province=" + province);/************ajax回調函數*********************/ajax.onreadystatechange = function () {if (ajax.readyState == 4) {if (ajax.status == 200) {//得到服務器端帶過來的XMLvar XMLDocument = ajax.responseXML;/**********解析XML文檔,使用DOM寫到下拉框中****************/var cities = XMLDocument.getElementsByTagName("city");//得到每一個cities節點的值,動態生成下拉框,添加到下拉框中for (var i = 0; i < cities.length; i++) {var value = cities[i].firstChild.nodeValue;//動態生成下拉框var optionElement = document.createElement("option");optionElement.innerHTML = value;//添加到下拉框中citySelect.appendChild(optionElement);}}}};}};</script></body>
</html>
Servlet
import java.io.IOException;
import java.io.PrintWriter;/**
* Created by ozc on 2017/5/17.
*/
@javax.servlet.annotation.WebServlet(name = "ProvinceServlet",urlPatterns = "/ProvinceServlet")
public class ProvinceServlet extends javax.servlet.http.HttpServlet {protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {//設置中文編碼request.setCharacterEncoding("UTF-8");String province = request.getParameter("province");//這里是返回的是XML,因此指定XML數據!response.setContentType("text/xml;charset=UTF-8");PrintWriter printWriter = response.getWriter();/****************返回XML文件給前臺**************/printWriter.write("<?xml version='1.0' encoding='UTF-8'?>");printWriter.write("<root>");if("廣東".equals(province)){printWriter.write("<city>廣州</city>");printWriter.write("<city>深圳</city>");printWriter.write("<city>中山</city>");}else if("湖南".equals(province)){printWriter.write("<city>長沙</city>");printWriter.write("<city>株洲</city>");printWriter.write("<city>湘潭</city>");printWriter.write("<city>岳陽</city>");}printWriter.write("</root>");System.out.println("1111");/*******事后操作*******/printWriter.flush();printWriter.close();}protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {this.doPost(request, response);}
}
效果:
XML方式總結
- 監聽下拉框的變化,如果變化了,那么就使用異步操作去訪問服務器,得到對應的數據返回給異步對象
- 異步對象解析服務器帶過來的數據,使用DOM編程把數據動態添加到頁面上
- 在Servlet上記得要指定返回的是XML的數據!
- 在前臺解析XML文檔的時候,不能直接使用innerHtml來得到節點的值,只能通過firstChild.nodeValue的方式獲取。
- 由于每次append到下拉框都會連續append,因此在響應事件的時候,把下拉框清零
- 把下拉框options的長度賦值為1,那么就是清零的操作了。
AJAX二級下拉聯動案例【JSON版】
前面我們已經使用過了XML作為數據載體在AJAX中與服務器進行交互。當時候我們的案例是二級聯動,使用Servlet進行控制
這次我們使用JSON作為數據載體在AJAX與服務器交互,使用三級聯動,使用Action進行控制....
- 省份-城市-區域三級聯動【Struts2 + JSON版】
分析
與上次是一樣的,只不過這次換了用JSON,使用Action控制罷了...
監聽下拉框的變動,使用異步對象與服務器進行交互。
前臺分析
- 監聽下拉框的變動
- 得到服務器返回的JSON數據
- 使用eval()進行解析,得到具體的對象
- 使用DOM編程把數據填充到對應的下拉框上
后臺分析
- 得到前臺發送過來的數據
- 判斷具體的數據是什么,給出對應的數據
- 使用Struts2提供的組件把數據封裝成JSON
- 返回給瀏覽器
監聽省份JSP頁面
<%--Created by IntelliJ IDEA.User: ozcDate: 2017/5/18Time: 13:36To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>使用JSON數據載體與服務器進行交互</title><script type="text/javascript" src="js/ajax.js"></script></head><body><%--############前臺頁面##############################--%><select name="province" id="provinceId"><option>請選擇省份</option><option>廣東</option><option>北京</option></select><select name="city" id="cityId"><option>請選擇城市</option></select><select name="area" id="areaId"><option>請選擇區域</option></select><%--############監聽省份##############################--%><script type="text/javascript">document.getElementById("provinceId").onchange= function () {// 得到選中的下拉框的值var provinceValue = this.options[this.selectedIndex].innerHTML;/***************ajax代碼*************************/if("請選擇省份" != provinceValue) {//每次訪問的時候,都要清空select的值var citySelect = document.getElementById("cityId");citySelect.options.length = 1;var ajax = createAJAX();var method = "post";var url = "${pageContext.request.contextPath}/province_findCityByProvince?time=" + new Date().getTime();ajax.open(method, url);ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");//顧及到發送的key、value值有很多,于是我們使用對象吧。ajax.send("bean.name=" + provinceValue);/***************等待服務器的響應,得到服務器返回的數據************************/ajax.onreadystatechange = function () {if(ajax.readyState==4) {if(ajax.status==200) {var jsonJava = ajax.responseText;//解析成是JS類型的JSONvar json = eval("(" + jsonJava + ")");//得到每個城市的值for(var i=0;i<json.city.length;i++) {var city = json.city[i];//動態創建option控件var option = document.createElement("option");option.innerHTML = city;citySelect.appendChild(option);}}}};}};</script> </body>
</html>
監聽省份Action
要想Struts2能夠把Action的數據封裝成JSON,就需要導入Struts2的開發包
- struts2-json-plugin-2.3.4.1.jar
在Action中對應的成員屬性需要給getter方法
import com.opensymphony.xwork2.ActionSupport;import java.util.ArrayList;
import java.util.List;/**
* Created by ozc on 2017/5/18.
*/
public class ProvinceAction extends ActionSupport{//自動封裝數據private Bean bean;public Bean getBean() {return bean;}public void setBean(Bean bean) {this.bean = bean;}//封裝城市的集合private List<String> city = new ArrayList<>();public List<String> getCity() {return city;}public String findCityByProvince() throws Exception {if ("廣東".equals(bean.getName())) {city.add("廣州");city.add("珠海");city.add("從化");} else if ("北京".equals(bean.getName())) {city.add("一環");city.add("二環");city.add("三環");city.add("四環");} else {System.out.println("沒有你選擇的地區");}return "ok";}
}
返回給前端的時候,數據是這樣子的:
效果
監聽城市JSP
<%--############監聽城市##############################--%><script type="text/javascript">document.getElementById("cityId").onchange = function () {//清空值var areaSelect = document.getElementById("areaId");areaSelect.options.length = 1;//得到選擇選中的下拉框的值var cityValue = this.options[this.selectedIndex].innerHTML;if(cityValue!="請選擇城市"){var ajax = createAJAX();var method = "post";var url = "${pageContext.request.contextPath}/province_findAreaByCity?time=" + new Date().getTime();ajax.open(method, url);ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");//顧及到發送的key、value值有很多,于是我們使用對象吧。ajax.send("bean.name=" + cityValue);/***************等待服務器的響應,得到服務器返回的數據************************/ajax.onreadystatechange = function () {if(ajax.readyState==4) {if(ajax.status==200) {var jsonJava = ajax.responseText;var json = eval("(" + jsonJava + ")");//得到每個地區的值for(var i=0;i<json.area.length;i++) {var area = json.area[i];//動態創建option控件var option = document.createElement("option");option.innerHTML = area;areaSelect.appendChild(option);}}}}};};</script>
Action頁面
public String findAreaByCity() throws Exception {if ("廣州".equals(bean.getName())) {area.add("白云區");area.add("黃浦區");area.add("蘿崗區");} else if ("珠海".equals(bean.getName())) {area.add("香江");area.add("拱北");area.add("EE");area.add("xx");} else {System.out.println("沒有你選擇的地區");}return "ok";}
最終效果:
總結
這次使用的是JSON作為數據載體與服務器進行交互,和XML本質上是沒有區別的。
只不過JSON是更加輕量級文本數據,在JavaScript能夠方便地獲取返回的數據
- 在Struts2中把Action數據封裝成JSON格式,返回給異步對象
- 需要導入jar包
- 在配置文件中配置繼承json包
- 返回的類型是json
- 如果使用POST時,發送的key、vaulue太多的話,我們可以使用bean進行封裝
- 當選中省份時,把城市和區域的下拉框清空,當選擇城市時,把區域的下拉框清空
總結圖
更多的文章可往:文章的目錄導航如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y