day11 Ajax&Axios&书城项目第五阶段
1. Ajax
1.1 服务器端渲染
1.2 Ajax渲染(局部更新)
1.3 前后端分离
真正的前后端分离是前端项目和后端项目分服务器部署,在我们这里我们先理解为彻底舍弃服务器端渲染,数据全部通过Ajax方式以JSON格式来传递
1.4 同步与异步
Ajax本身就是Asynchronous JavaScript And XML的缩写,直译为:异步的JavaScript和XML。在实际应用中Ajax指的是:不刷新浏览器窗口,不做页面跳转,局部更新页面内容的技术。
『同步』和『异步』是一对相对的概念,那么什么是同步,什么是异步呢?
1.4.1 同步
多个操作按顺序执行,前面的操作没有完成,后面的操作就必须等待。所以同步操作通常是串行的。
1.4.2 异步
多个操作相继开始并发执行,即使开始的先后顺序不同,但是由于它们各自是在自己独立的进程或线程中完成,所以互不干扰,谁也不用等谁。
2. Axios
2.1 Axios简介
使用原生的JavaScript程序执行Ajax极其繁琐,所以一定要使用框架来完成。而Axios就是目前最流行的前端Ajax框架。
Axios官网:http://www.axios-js.com/
使用Axios和使用Vue一样,导入对应的*.js文件即可。官方提供的script标签引入方式为:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
我们可以把这个axios.min.js文件下载下来保存到本地来使用。
2.2 Axios基本用法
2.2.1 在前端页面引入开发环境
<script src="js/axios.min.js"></script>
<script src="js/vue.js"></script>
2.2.2 发送普通请求参数
① 前端代码
HTML标签:
<div id="box">
<a href="hello">HelloServlet</a>
<div v-html="message"></div>
<hr>
<button @click="send1()"> 点我发送异步请求1</button>
<br>
</div>
Vue+axios代码:
var vue = new Vue({
"el":"#box",
"data":{
"message":""
},
"methods":{
"send1":function () {
// 发起异步请求
axios({
"url":"s1", // 请求的地址
"method":"post", // 请求的方式
"params":{ // 请求时携带的参数
"uname":"zs",
"pwd":"123"
}
// 请求成功 执行then
}).then(function (success){
console.log(success);
console.log(success.config);
console.log(success.data);
// console.log(this); // axios里面this表示window对象
vue.message = success.data;
// 请求失败 执行catch
}).catch(function (reason) {
console.log(reason);
// 失败的详细信息
console.log(reason.response);
console.log(reason.response.config);
console.log(reason.response.data);
vue.message = reason.response.data;
})
}
}
})
注意:所有请求参数都被放到URL地址后面了,哪怕我们现在用的是POST请求方式。
② 后端代码
package com.atguigu.servlet;
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;
@WebServlet("/s1")
public class AxiosServlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-------------AxiosServlet1 doGet()---------------");
// 解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
int i = 10/0;
resp.getWriter().println("AxiosServlet1 doGet Success");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-------------AxiosServlet1 doPost()---------------");
// 解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("AxiosServlet doPost Success");
}
}
servlet获取axios传递的的参数
html代码:
<button @click="send2()"> 点我发生异步请求2</button>
vue+axios代码:
"send2":function () {
axios({
"url":"s2",
"method":"post",
"params":{
"username":"李白",
"password": "123",
"gender": "男"
}
}).then(function (value) {
console.log(value);
// 获取服务端反馈的数据
console.log(value.data);
})
}
servlet代码:
package com.atguigu.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
@WebServlet("/s2")
public class AxiosServlet2 extends HelloServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("----------AxiosServlet2 doPost()--------");
// 解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 获取axios传来的数据
Map<String, String[]> map = req.getParameterMap();
map.forEach((k,v) -> System.out.println(k + "--->" + Arrays.toString(v)));
resp.getWriter().println("AxiosServlet2 Success");
}
}
③ axios程序接收到的响应对象结构
属性名 | 作用 |
---|---|
config | 调用axios(config对象)方法时传入的JSON对象 |
data | 服务器端返回的响应体数据 |
headers | 响应消息头 |
request | 原生JavaScript执行Ajax操作时使用的XMLHttpRequest |
status | 响应状态码 |
statusText | 响应状态码的说明文本 |
④ 服务器端处理请求失败后
catch(error => { // catch()服务器端处理请求出错后,会调用
console.log(error); // error就是出错时服务器端返回的响应数据
console.log(error.response); // 在服务器端处理请求失败后,获取axios封装的JSON格式的响应数据对象
console.log(error.response.status); // 在服务器端处理请求失败后,获取响应状态码
console.log(error.response.statusText); // 在服务器端处理请求失败后,获取响应状态说明文本
console.log(error.response.data); // 在服务器端处理请求失败后,获取响应体数据
});
在给catch()函数传入的回调函数中,error对象封装了服务器端处理请求失败后相应的错误信息。其中,axios封装的响应数据对象,是error对象的response属性。response属性对象的结构如下图所示:
可以看到,response对象的结构还是和then()函数传入的回调函数中的response是一样的:
回调函数:开发人员声明,但是调用时交给系统来调用。像单击响应函数、then()、catch()里面传入的都是回调函数。回调函数是相对于普通函数来说的,普通函数就是开发人员自己声明,自己调用:
function sum(a, b) { return a+b; } var result = sum(3, 2); console.log("result="+result);
2.3 发送请求体JSON和返回JSON
2.3.1 前端代码
HTML代码:
<button @click="send3()"> 点我发生异步请求3</button>
Vue+axios代码:
"send3":function () {
axios({
"url":"s3",
"method":"post", // 如果传递json类型的数据 必须使用post
"data":{
"username": "李清照",
"password":"123456"
}
}).then(function (success){
console.log(success.data);
console.log(success.data.username);
})
}
2.3.2 后端代码
① 加入Gson包
Gson是Google研发的一款非常优秀的JSON数据解析和生成工具,它可以帮助我们将数据在JSON字符串和Java对象之间互相转换。
② User类
package com.atguigu.pojo;
public class User {
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
③ Servlet代码
package com.atguigu.servlet;
import com.atguigu.pojo.User;
import com.google.gson.Gson;
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.BufferedReader;
import java.io.IOException;
@WebServlet("/s3")
public class AxiosServlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("------------AxiosServlet3 doPost()---------");
// 解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// 缓冲字符输入流
BufferedReader reader = req.getReader();
StringBuffer sbf = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null){
sbf.append(line);
}
System.out.println("sbf = " + sbf); // sbf = {"username":"李清照","password":"123456"}
// 创建Gson对象
Gson gson = new Gson();
// 将json格式的字符串转为对象
User user = gson.fromJson(sbf.toString(), User.class);
System.out.println("user = " + user); // user = User{username='李清照', password='123456'}
User user2 = new User("王安石", "123");
// 将对象转为json格式的字符串
String json = gson.toJson(user2);
System.out.println("json = " + json);
// 返回一个json字符串
resp.getWriter().println(json); // json = {"username":"王安石","password":"123"}
// resp.getWriter().println("AxiosServlet3 Success");
}
}
P.S.:看着很麻烦是吧?别担心,将来我们有了SpringMVC之后,一个@RequestBody注解就能够搞定,非常方便!
2.4 jackson的使用介绍
2.4.1 jackson的简介
jackson是Java中比较常用的JSON解析的工具包,SpringMVC和SpringBoot中默认支持的就是jackson
2.4.2 jackson的使用
① 引入jar包
② API介绍
- 创建ObjectMapper对象
ObjectMapper objectMapper = new ObjectMapper();
- 调用
writeValueAsString(obj)
方法将对象转成json字符串 - 调用
mapper.readValue(text, Class);
将json字符串转成对象
html代码:
<button @click="send4()"> 点我发送异步请求4</button>
vue+axios代码:
"send4":function () {
axios({
"url":"s4",
"method":"post",
"data":{
"username":"杜甫",
"password":"abcdefg"
}
}).then(function (value) {
console.log(value);
console.log(value.data);
console.log(value.data.username);
})
}
servlet代码:
package com.atguigu.servlet;
import com.atguigu.pojo.User;
import com.atguigu.utils.JSONUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
@WebServlet("/s4")
public class AxiosServlet4 extends HelloServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 解决乱码问题
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
BufferedReader reader = req.getReader();
StringBuffer sbf = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null){
sbf.append(line);
// 创建ObjectMapper对象 对象 <------------> json格式字符串
ObjectMapper mapper = new ObjectMapper();
// json格式字符串 -----> 对象
User user = mapper.readValue(sbf.toString(), User.class);
}
System.out.println("user = " + user); // user = User{username='杜甫', password='abcdefg'}
// 对象 ----> json格式字符串
User user1 = new User("安其拉", "123");
String json = mapper.writeValueAsString(user1);
System.out.println("json = " + json); // json = {"username":"安其拉","password":"123"}
resp.getWriter().println(json);
}
}
2.5 封装Json工具类用于获取json格式的请求参数以及向客户端响应json字符串
package com.atguigu.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
/*
1.读入json格式字符串转为对象
2.将对象变为json格式字符串进行响应
*/
public class JSONUtils {
/**
* 将对象百年未json格式字符串进行响应
* @param resp
* @param obj
*/
public static void writeJson(HttpServletResponse resp, Object obj){
try {
ObjectMapper mapper = new ObjectMapper();
// 转为json格式字符串
String s = mapper.writeValueAsString(obj);
// 进行反馈
resp.getWriter().println(s);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 读入json格式字符串转为对象
* @param req
* @param clazz
* @param <T>
* @return
*/
public static <T> T parseJsonToObject(HttpServletRequest req, Class<T> clazz){
BufferedReader br = null;
try {
br = req.getReader();
StringBuffer sbf = new StringBuffer();
String line = "";
while ((line = br.readLine()) != null){
sbf.append(line);
}
ObjectMapper mapper = new ObjectMapper();
T value = mapper.readValue(sbf.toString(), clazz);
return value;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}finally {
if (br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
}
}
2.6 总结
2.6.1 同步请求和异步请求的区别
- 是否刷新、跳转页面
- 是否局部刷新
- 是否能同时发送多个请求
2.6.2 axios的使用
- 发送请求
axios({
"method":"POST",
"url":"路径",
"params"/"data":{}//请求参数
})
- 处理响应数据(服务器端没有异常的情况下)
.then(response => {//处理响应数据})
- 处理服务器端的异常
.catch(error => {//处理异常数据})
2.6.3 请求参数的类型
- 普通类型参数
/项目名/demo01?username=aobama&password=123456&gender=male
- JSON类型(请求体)
{"username":"aobama","password":"123456","gender":"male"}
2.6.4 Servlet中获取不同类型的参数
- 获取普通类型参数
1. request.getParameter(name)
2. request.getParameterValues(name)
3. request.getParameterMap()
- 获取JSON请求体类型的参数
第一步: 通过字符输入流,获取json请求体(字符串)
第二步: 解析json,将json字符串的内容封装到JavaBean对象
其实使用工具类就很简单了: JsonUtils.parseJsonToObject(request,JavaBean.class);
2.6.5 Servlet中将对象转成json字符串响应给客户端
在Servlet中使用工具类: JsonUtils.writeJson(response,object);
在前端js代码.then(response => {
//1. response.data 就相当于获取了服务器端响应过来的整个json字符串
//2. 想要获取json中的哪个值就使用response.data.key就能获取了
})
3. 书城项目第五阶段
3.1 注册页面用户名唯一性检查优化
3.1.1 准备工作同步请求
- 复制module
3.1.2 加入Ajax开发环境
① 前端所需axios库
② 后端所需jackson库
3.1.3 拷贝Json工具类
3.2 封装CommonResult
3.1 模型的作用
在整个项目中,凡是涉及到给Ajax请求返回响应,我们都封装到CommonResult类型中。
3.2 模型的代码
package com.atguigu.pojo;
/*
ajax通用的反馈结果
*/
public class CommonResult {
/*
服务器端处理请求的标识
true:成功
false:失败
*/
private boolean flag;
// 当服务器端处理请求失败的时候要响应给客户端的错误信息
private String message;
/*
结果集数据
*/
private Object resultData;
// 请求失败
public static CommonResult error(){
return new CommonResult().setFlag(false);
}
// 请求失败 携带错误信息
public static CommonResult error(String message){
return new CommonResult().setFlag(false).setMessage(message);
}
// 请求成功
public static CommonResult ok(){
return new CommonResult().setFlag(true);
}
// 请求成功 携带data数据
public static CommonResult ok(Object data){
return new CommonResult().setFlag(true).setResultData(data);
}
private CommonResult() {
}
public boolean isFlag() {
return flag;
}
public CommonResult setFlag(boolean flag) {
this.flag = flag;
return this;
}
public String getMessage() {
return message;
}
public CommonResult setMessage(String message) {
this.message = message;
return this;
}
public Object getResultData() {
return resultData;
}
public CommonResult setResultData(Object resultData) {
this.resultData = resultData;
return this;
}
}
各个属性的含义:
属性名 | 含义 |
---|---|
flag | 服务器端处理请求的结果,取值为true或者false |
message | 服务器端处理请求失败之后,要响应给客户端的数据 |
resultData | 服务器端处理请求成功之后,需要响应给客户端的数据 |
3.3 模型的好处
- 作为整个团队开发过程中,前后端交互时使用的统一的数据格式
- 有利于团队成员之间的协助,提高开发效率
3.3 功能实现
3.3.1 定位功能的位置
在用户输入用户名之后,立即检查这个用户名是否可用。
3.3.2 思路
① 给用户名输入框绑定的事件类型
结论:不能在针对username设定的watch中发送Ajax请求。
原因:服务器端响应的速度跟不上用户输入的速度,而且服务器端异步返回响应数据,无法保证和用户输入的顺序完全一致。此时有下面几点问题:
- 给服务器增加不必要的压力
- 用户输入的数据在输入过程中是不断发生变化的
- 响应数据和输入顺序不对应,会发生错乱
解决办法:绑定的事件类型使用change事件。
② 流程图
3.3.3 代码实现
① 在当前页面引入axios库文件
<script src="static/script/axios.min.js"></script>
② 给用户名输入框绑失去焦点事件
<input type="text" name="username" placeholder="请输入用户名" v-model.trim="username" @blur="checkUserName()"/>
③ JavaScript代码
var vue = new Vue({
"el": "#box",
"data":{
// 用户名信息
"username":"[[${param.username}]]",
"usernameErrMsg":"用户名应为3~16位数字和字母组成",
"usernameCss":{
"visibility":"hidden"
},
// 密码信息
"password":"",
"passwordErrMsg":"密码的长度至少为5位",
"passwordCss":{
"visibility":"hidden"
},
// 确认密码信息
"dbpassword":"",
"dbpasswordErrMsg":"密码两次输入不一致",
"dbpasswordCss":{
"visibility":"hidden"
},
// 邮箱信息
"email":"[[${param.email}]]",
"emailErrMsg":"请输入正确的邮箱格式",
"emailCss":{
"visibility":"hidden"
},
// 验证码信息
"code":"",
"imgPath":"KaptchaServlet",
"codeErrMsg":"验证码为4~6位数字和字母组成",
"codeCss":{
"visibility":"hidden"
}
},
"methods":{
"changeImg":function (){
//Math.random();目的是让data中 imgPath的值每次点击都不一样 这样才会重新赋值 才会发起新的请求
this.imgPath = "KaptchaServlet?a=" + Math.random();
},
"checkUserName":function (){
// 显示提示信息
this.usernameCss = {"visibility":"visible"};
// 1.获取用户名的值
var username = this.username;
// 2.定义正则表达式
var reg = /^[0-9a-zA-Z]{3,16}/;
// 3.使用正则校验
if (reg.test(username)){
// 3.1 发起异步请求
axios({
"url":"user",
"method":"post",
"params":{
"method":"checkUserName",
"username":this.username
}
}).then(function (value) {
console.log(value.data);
if (value.data.flag){
vue.usernameErrMsg = "√";
}else{
vue.usernameErrMsg = value.data.message;
}
vue.usernameFlag = value.data.flag;
})
}else{
// 3.2 不满足
this.usernameErrMsg = "用户名应为3~16位数字和字母组成";
vue.usernameFlag = false;
}
},
"checkPassWord":function (){
// 显示提示信息
this.passwordCss = {"visibility": "visible"};
// 1.获取密码的值
var password = this.password;
// 2.定义正则表达式
var reg = /\w{5,}/;
// 3.使用正则校验
if (reg.test(password)){
// 3.1 满足条件
this.passwordErrMsg = "√";
return true;
}else{
this.passwordCss = "密码的长度至少为5位"
}
},
"checkDbPassWord":function (){
// 显示提示信息
this.dbpasswordCss = {"visibility":"visible"}
// 1.获取确认密码的值
var dbpassword = this.dbpassword;
// 2.判断确认密码是否为空
if (dbpassword == ""){
this.dbpasswordErrMsg = "密码长度不能为空";
return false;
}
if (this.password == dbpassword){
this.dbpasswordErrMsg = "√";
return true;
}else{
this.dbpasswordErrMsg = "密码两次输入不一致";
return false;
}
},
"checkEmail":function (){
// 显示提示信息
this.emailCss = {"visibility": "visible"}
// 1.获取邮箱的值
var email = this.email;
// 2.定义正则表达式
var reg = /^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+[\.]{1})+[a-zA-Z]+$/;
// 3.使用正则校验
if (reg.test(email)){
this.emailErrMsg = "√";
return true;
}else{
this.emailErrMsg = "请输入正确的邮箱格式";
return false;
}
},
"checkCode":function (){
// 显示提示信息
this.codeCss = {"visibility": "visible"}
// 1.获取验证码的值
var code = this.code;
// 2.定义正则表达式
var reg = /^[0-9a-zA-Z]{4,6}/;
// 3.使用正则校验
if (reg.test(code)){
this.codeErrMsg = "√";
return true;
}else{
this.codeErrMsg = "验证码为4~6位数字和字母组成";
return false;
}
},
"register":function (){
// 使用单& 会全部执行一边 提示所有报错
var flag = this.usernameFlag & this.checkPassWord() & this.checkDbPassWord() & this.checkEmail() & this.checkCode();
//只要有一个校验失败 则 不能提交 阻止默认事件
if(!flag){
event.preventDefault();
}
}
}
})
④ UserServlet
// 校验用户名是否可以使用
protected void checkUserName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
CommonResult result = null;
try {
// 获取请求参数username的值
String username = req.getParameter("username");
// 调用业务层方法校验用户名是否可以使用
service.checkUserName(username);
// 没有异常 表示用户名可用
result = CommonResult.ok();
// 将json对象转换为json字符串反馈给浏览器
} catch (Exception e) {
e.printStackTrace();
// 出现异常 表示用户名已存在
result = CommonResult.error(e.getMessage());
}
// 将result对象转成json字符串 响应给客户端
JSONUtils.writeJson(resp, result);
}
⑤ UserService
@Override
public void checkUserName(String username) {
// 1.创建持久层对象
UserDao userDao = new UserDaoImpl();
User user = userDao.findUserByName(username);
if (user != null){
throw new RuntimeException("用户名【 "+ username +"】已存在");
}
}