AJAX
什么是AJAX
Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)
AJAX是多个技术联合实现的产物
AJAX是一个浏览器客户端上的前端技术
只要是做web开发,B/S架构的,不管服务器端使用什么编程语言,前端AJAX都是要学习的
异步和同步有什么区别
A线程和B线程,并发执行,谁也不等谁,这就是异步
A线程和B线程,在A线程执行的时候,B线程需要等待,或者在B线程执行的时候,A线程需要等待,这就是同步
传统的请求和AJAX请求有什么区别
传统的请求:都是同步的
AJAX请求:可以做到同步
AJAX经典案例
Google auto_complete(输入框自动补全)
Google Map(谷歌地图)
浏览器支持多线程
浏览器本身这个软件是支持多线程并发的,其中ajax请求就是一个线程;
一个页面上可以同时发送多个ajax请求,多个ajax请求对象浏览器多个线程;
当整个浏览器采用的是传统的请求的时候,请求只要一发送,整个浏览器窗口会锁定,无法点击按钮,并且浏览器会将整个窗口当中的数据完全清除,迎接新的页面
AJAX解决的主要问题
解决页面的局部刷新问题
使用AJAX可以在同一个网页中并发的发送多个请求,请求与请求之间互不等待,互不干扰,这样可以提高用户的体验
AJAX 请求
发送ajax请求的代码包括4步
- 创建ajax核心对象XMLHttpRequest(浏览器内置对象,可以直接使用)
- 注册回调函数
- 开启浏览器和服务器之间的通道
- 发送请求
AJAX对象属性
-
readyState
XMLHttpRequest对象在请求和响应的过程中,该对象的readyState属性值从 0 到 4 发生变化
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪 -
status
status属性,HTTP响应状态码
200: “OK”
404: 未找到页面 -
responseText
responseText属性:响应回来的text
XMLHttpRequest.open()参数
open(method,url,async)
- method:请求的类型;GET 或 POST
- url:文件在服务器上的位置
- async:true(异步)或 false(同步)
async为true时,表示支持异步,同时可以发送多个ajax请求,多个ajax可以同时发送,请求并发
async为false时,表示支持同步,同时仅可发送一个ajax请求,ajax请求执行完后才能发送新的ajax的请求
大部分情况下,都是async为true,支持异步,
但是在一个表单中,有多个项目需要发送ajax请求进行验证的时候,必须等待所有表单项验证完毕后,才允许用户点击注册,此时注册按钮的ajax必须使用同步ajax
AJAX请求(GET)
//1. 创建ajax核心对象XMLHttpRequest(浏览器内置对象,可以直接使用)
var xhr;
//判断浏览器是否支持XMLHttpRequest对象
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
//IE 5/6版本不支持XMLHttpRequest,仅支持ActiveXObject对象
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) { //服务器端响应结束
if (xhr.status == 200) {
alert("服务器响应成功!");
} else {
//弹出错误代码
alert(xhr.status);
}
}
}
//3. 开启浏览器和服务器之间的通道
/**
* xhr.open(method,url,async);
* method:指定请求方式为get/post
* url:请求路径
* async:true/false,true表示支持异步,false表示支持同步
*
*
*/
xhr.open("GET","/ajax/checkusername.do?username=",true);
//4. 发送请求
xhr.send();
AJAX请求(POST)
POST请求要在第三步xhr.open()后增加以下代码模拟表单POST提交,因为只有表单才能POST提交
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
POST请求,请求体要在第四步xhr.send()中添加参数,如
xhr.send("test=ajax&method=post");
//1. 创建ajax核心对象XMLHttpRequest
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
alert("AJAX OK");
}
}
//3. 开启通道
xhr.open("POST","/ajax/userLogin.post",true);
//只有表单才能提交POST请求,所以模拟表单,需要加上以下代码
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
//4. 发送请求
//POST请求提交数据在send()方法中提交
xhr.send("test=ajax&method=post");
AJAX请求方式例子
GTE请求
用户注册界面,当用户鼠标失去用户名输入框焦点时,发送ajax请求判断用户名是否被注册
已注册显示红色字体
未注册显示绿色字体
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>使用ajax发送get请求验证用户名是否存在</title>
</head>
<body>
<script type="text/javascript">
function checkUsername(username) {
// 1. 创建ajax核心对象XMLHttpRequest(浏览器内置对象,可以直接使用)
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2. 注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) { //服务器端响应结束
if (xhr.status == 200) {
var nameTipMsg = document.getElementById("nameTipMsg");
//在浏览器端使用xhr对象接收服务器端响应回来的文本
var text = xhr.responseText;
nameTipMsg.innerHTML = text;
} else {
//弹出错误代码
alert(xhr.status);
}
}
}
// 3. 开启浏览器和服务器之间的通道
xhr.open("GET","/ajax/checkusername.do?username="+username,true);
// 4. 发送请求
xhr.send();
}
</script>
用户名<input type="text" name="username" onblur="checkUsername(this.value)">
<span id="nameTipMsg"></span><br>
密码<input type="password" name="password">
</body>
</html>
POST请求
模拟用户登陆
AJAX POST请求,后台返回json字符串,
当{"success":true}
时登陆成功,显示绿色登陆成功提示文字;
当{"success":false
}时登陆成功,显示红色登陆失败提示文字
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>使用ajax发送POST请求验证用户登陆是否成功</title>
</head>
<body>
<script type="text/javascript">
function userLogin() {
var username = document.getElementById("username");
var password = document.getElementById("password");
//1. 创建ajax核心对象XMLHttpRequest
var xhr ;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
//2. 注册回调函数
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var jsonString = xhr.responseText;//这里的jsonString只是一个普通的字符串
eval("var jsonObj = " + jsonString);//将字符串作为js代码执行
let tipMsg = document.getElementById("tipMsg");
if (jsonObj.success) {
tipMsg.innerHTML = "<font color='green'>登陆成功!</font>";
} else {
tipMsg.innerHTML = "<font color='red'>登陆失败!</font>";
}
}
}
//3. 开启通道
xhr.open("POST","/ajax/userLogin.post",true);
//只有表单才能提交POST请求,所以模拟表单,需要加上以下代码
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
//4. 发送请求,POST请求提交数据在send()方法中提交
xhr.send("username="+username.value+"&password="+password.value);
}
</script>
用户名<input type="text" id="username"><span id="tipMsg"></span><br>
密码<input type="password" id="password"><br>
<input type="button" value="登陆" onclick="userLogin()">
</body>
</html>
GET请求缓存问题
问题:get请求,第一次会请求服务器,之后再访问的时候就直接走浏览器缓存,而不再请求服务器了
解决:可以在get请求的URL加上时间戳,这样每次请求的URL都不一样,这样每次都会请求服务器了
var timeStamp = new Date().getTime();//获取毫秒数
xhr.open("GET","/ajax/checkusername.get?_="+timeStamp+"&username="+username,true);
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>使用ajax发送get请求验证用户名是否存在</title>
</head>
<body>
<script type="text/javascript">
function checkUsername(username) {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var nameTipMsg = document.getElementById("nameTipMsg");
var text = xhr.responseText;
nameTipMsg.innerHTML = text;
} else {
alert(xhr.status);
}
}
}
// 3. 开启浏览器和服务器之间的通道
// 解决get换出问题,在URL中加上时间戳
var timeStamp = new Date().getTime();//获取毫秒数
xhr.open("GET","/ajax/checkusername.get?_="+timeStamp+"&username="+username,true);
// 4. 发送请求
xhr.send();
}
</script>
用户名<input type="text" name="username" onblur="checkUsername(this.value)">
<span id="nameTipMsg"></span><br>
密码<input type="password" name="password">
</body>
</html>
省市联动案例
打开网页时初始化,浏览器发送请求获取省份列表;
当用户选择省份时,发送请求获取此省份的市列表
- 使用 自定义
src/Utils.JDBCUtils
工具类 - 使用 Druid数据库连接池
资源与准备
使用的工具类与jar包
自定义src/Utils.JDBCUtils
工具类
自定义src/prop/druid.properties
配置文件用来配置druid数据库连接池
自定义数据库SQL/ajax.sql
文件
官方的JDBC驱动jar包/WEB-INF/lib/mysql-connector-java-5.1.7-bin.jar
官方的druid数据库连接池jar包/WEB-INF/lib/druid-1.1.23.jar
使用的资源详情
src/Utils.JDBCUtils
JDBC工具类
package Utils;
import java.sql.*;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;
/**
* @version 1.0
* @Description:
* <p>JDBCUtils 工具类</p>
* <p>是一个工具类,提供了数据库连接池和常用的资源关闭方法</p>
* @className JDBCUtils
* @author: Mango
* @date: 2020-09-06 21:22
*/
public class JDBCUtils {
/**
* Druid连接池对象
*/
private static DruidDataSource druidDataSource = new DruidDataSource();
/**
* 静态创建一个Druid连接池对象并通过配置文件设置相关配置
*/
static {
ResourceBundle bundle = ResourceBundle.getBundle("prop/druid");
druidDataSource.setDriverClassName(bundle.getString("driverClassName"));
druidDataSource.setUrl(bundle.getString("url"));
druidDataSource.setUsername(bundle.getString("username"));
druidDataSource.setPassword(bundle.getString("password"));
druidDataSource.setInitialSize(Integer.parseInt(bundle.getString("initialSize")));
druidDataSource.setMaxActive(Integer.parseInt(bundle.getString("maxActive")));
druidDataSource.setMaxActive(Integer.parseInt(bundle.getString("maxWait")));
}
/**
* 获取连接
* <p>从druid连接池中获取一个连接</p>
* @param
* @return 返回一个Connection连接对象
*/
public static Connection getConnection() throws SQLException {
return druidDataSource.getConnection();
}
/**
* 关闭连接和资源
* <p>关闭Connection连接、关闭PreparedStatement预编译SQL语句对象、关闭ResultSet查询结果集</p>
* @param conn 数据库连接对象
* @param ps 预编译SQL语句对象
* @param rs 查询结果集
* @return void
*/
public static void closeAll(Connection conn, PreparedStatement ps, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 关闭连接和资源
* <p>关闭Connection连接、关闭PreparedStatement预编译SQL语句对象</p>
* @param conn 数据库连接对象
* @param ps 预编译SQL语句对象
* @return void
*/
public static void closeAll(Connection conn, PreparedStatement ps) {
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 关闭数据库连接
* <p>关闭数据库连接对象conn</p>
* @param conn 数据连接对象
* @return void
*/
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 关闭statement
* <p>关闭数据库连接对象conn</p>
* @param statement statement对象
* @return void
*/
public static void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 关闭ResultSet查询结果集
* <p>关闭查询结果集对象ResultSet rs</p>
* @param rs 查询结果集
* @return void
*/
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
src/prop/druid.properties
数据库连接池配置文件
url=jdbc:mysql://localhost:3306/messagewall?useUnicode=true&characterEncoding=utf8
username=root
password=admin
driverClassName=com.mysql.jdbc.Driver
initialSize=1
maxActive=3
maxWait=60000
SQL/ajax.sql
数据库文件
数据库名为 ajax
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 50562
Source Host : localhost:3306
Source Schema : ajax
Target Server Type : MySQL
Target Server Version : 50562
File Encoding : 65001
Date: 16/10/2020 23:15:10
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_city
-- ----------------------------
DROP TABLE IF EXISTS `t_city`;
CREATE TABLE `t_city` (
`code` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pcode` char(3) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`code`) USING BTREE,
INDEX `pcode`(`pcode`) USING BTREE,
CONSTRAINT `t_city_ibfk_1` FOREIGN KEY (`pcode`) REFERENCES `t_province` (`code`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_city
-- ----------------------------
INSERT INTO `t_city` VALUES (1, '保定市', '003');
INSERT INTO `t_city` VALUES (2, '石家庄市', '003');
INSERT INTO `t_city` VALUES (3, '廊坊市', '003');
INSERT INTO `t_city` VALUES (4, '张家口市', '003');
INSERT INTO `t_city` VALUES (5, '济南市', '001');
INSERT INTO `t_city` VALUES (6, '淄博市', '001');
INSERT INTO `t_city` VALUES (7, '青岛市', '001');
INSERT INTO `t_city` VALUES (8, '烟台市', '001');
INSERT INTO `t_city` VALUES (9, '太原市', '002');
INSERT INTO `t_city` VALUES (10, '运城市', '002');
INSERT INTO `t_city` VALUES (11, '大同市', '002');
INSERT INTO `t_city` VALUES (12, '临汾市', '002');
INSERT INTO `t_city` VALUES (13, '郑州市', '004');
INSERT INTO `t_city` VALUES (14, '开封市', '004');
INSERT INTO `t_city` VALUES (15, '洛阳市', '004');
INSERT INTO `t_city` VALUES (16, '平顶山市', '004');
-- ----------------------------
-- Table structure for t_province
-- ----------------------------
DROP TABLE IF EXISTS `t_province`;
CREATE TABLE `t_province` (
`code` char(3) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_province
-- ----------------------------
INSERT INTO `t_province` VALUES ('001', '山东省');
INSERT INTO `t_province` VALUES ('002', '山西省');
INSERT INTO `t_province` VALUES ('003', '河北省');
INSERT INTO `t_province` VALUES ('004', '河南省');
SET FOREIGN_KEY_CHECKS = 1;
业务代码
/WEB-INF/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>省市联动</title>
</head>
<body>
<select id="provinceList">
<option value='-1'>请选择省份区</option>
</select>
<select id="cityList">
<option value=''>请选择市区</option>
</select>
</body>
</html>
<script type="text/javascript" src="index.js"></script>
/WEB-INF/index.js
window.onload = function () {
//省、市下拉列表对象
let provinceListObj = document.getElementById("provinceList");
let cityListObj = document.getElementById("cityList");
//给省下拉列表注册onchange
provinceListObj.onchange = function () {
listCity();
}
/**
* 获取省列表函数
* 向服务器发送请求,获取json字符串,然后将数据设置到 省 下拉列表中
*/
function listProvince() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var jsonString = xhr.responseText;
eval("var arr = " + jsonString);
// 遍历json数组
var html = "<option value='-1'>请选择省份</option>";
for (var i=0; i<arr.length; i++) {
var provinceJson = arr[i];
html += "<option value='"+provinceJson.code+"'>"+provinceJson.name+"</option>"
}
provinceListObj.innerHTML = html;
}
}
xhr.open("GET","/ajax_03/province/list.do",true);
xhr.send();
}
/**
* 获取城市列表函数
* 向服务器发送请求,获取json字符串,然后将数据设置到 市 下拉列表中
* 此函数在省下拉列表改变时调用
*/
function listCity() {
//判断,如果选择了 省 默认提示选项,则清空 市 下拉列表中的项
if (provinceListObj.value == "-1") {
cityListObj.innerHTML = "<option value=''>请选择市区</option>";
return;
}
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var jsonString = xhr.responseText;
eval("var arr = " + jsonString);
// 遍历json数组
var html = "<option value=''>请选择市区</option>";
for (var i=0; i<arr.length; i++) {
var cityJson = arr[i];
html += "<option value='"+cityJson.code+"'>"+cityJson.name+"</option>"
}
cityListObj.innerHTML = html;
}
}
var pcode = provinceListObj.value;
xhr.open("GET","/ajax_03/city/list.do?pcode="+pcode,true);
xhr.send();
}
//执行此函数获取省列表
listProvince();
}
src/action.CityListAction.java
package action;
import utils.JDBCUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.*;
/**
* <p>获取 市 列表,拼接json,将json返回浏览器</p>
* @className ProvinceListAction
* @date: 2020-10-16 21:39
*/
@WebServlet("/city/list.do")
public class CityListAction extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String pcode = request.getParameter("pcode");
//局部变量不存在线程安全问题,所以建议使用StringBuilder,效率高
/*
[
{"code":"001","name":"济南市"},
{"code":"002","name":"淄博市"},
{"code":"003","name":"青岛市"},
{"code":"003","name":"烟台市"}
]
*/
StringBuilder json = new StringBuilder();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement("select * from t_city where pcode=?");
ps.setString(1,pcode);
rs = ps.executeQuery();
json.append("[");
while (rs.next()) {
String code = rs.getString("code");
String name = rs.getString("name");
// {"code":"001","name":"济南市"},
json.append("{");
json.append("\"code\":");
json.append("\""+code+"\",");
json.append("\"name\":");
json.append("\""+name+"\"");
json.append("},");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.closeAll(conn,ps,rs);
}
//截取字符串,去掉最后一个多余的逗号,
String endJson = json.substring(0, json.length() - 1) + "]";
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(endJson);
}
}
src/action.ProvinceListAction.java
package action;
import utils.JDBCUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.*;
/**
* <p>获取 省 列表,拼接json,将json返回浏览器</p>
* @className ProvinceListAction
* @date: 2020-10-16 21:39
*/
@WebServlet("/province/list.do")
public class ProvinceListAction extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//局部变量不存在线程安全问题,所以建议使用StringBuilder,效率高
/*
[
{"code":"001","name":"山东省"},
{"code":"002","name":"山西省"},
{"code":"003","name":"河南省"}
]
*/
StringBuilder json = new StringBuilder();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement("select code,name from t_province order by code asc");
rs = ps.executeQuery();
json.append("[");
while (rs.next()) {
String code = rs.getString("code");
String name = rs.getString("name");
// {"code":"001","name":"山东省"},
json.append("{");
json.append("\"code\":");
json.append("\""+code+"\",");
json.append("\"name\":");
json.append("\""+name+"\"");
json.append("},");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.closeAll(conn,ps,rs);
}
//截取字符串,去掉最后一个多余的逗号,
String endJson = json.substring(0, json.length() - 1) + "]";
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(endJson);
}
}