⭐表示重要。
第一章:Ajax 回顾(⭐)
1.1 Ajax 概述
- ① 服务器端渲染:
- ② Ajax 渲染(局部更新):
1.2 前后端分离
- 彻底舍弃服务器端渲染,数据全部通过 Ajax 方式以 JSON 格式来传递。
1.3 同步和异步
1.3.1 概述
- Ajax 本身就是 Asynchronous JavaScript And XML 的缩写,直译为:异步的 JavaScript 和 XML 。在实际应用中 Ajax 指的是:
不刷新浏览器窗口
、不做页面跳转
、局部更新页面内容
的技术。 同步
和异步
是相对的概念。
1.3.2 同步
- 多个操作
按照顺序执行
,前面的操作没有完成,后面的操作就必须等待
,所以同步操作通常是串行
的。
1.3.3 异步
- 多个操作相继开始
并发执行
,即使开始的先后顺序不同,但是由于它们各自是在自己独立的进程或线程中
完成,所以互不干扰,谁也不用等待谁
。
1.4 Axios
- 使用远程的 JavaScript 执行 Ajax 非常繁琐,所以一般会使用框架来完成,而 Axios 就是目前前端最流行的 Ajax 框架。
- 官网。
- 使用 Axios 和使用 Vue 一样,导入对应的 *.js 文件即可。官方提供的 script 标签引入方式为:
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
第二章:实验一(⭐)
2.1 需求
- 请求:发送普通请求参数。
- 响应:服务器端返回普通文本。
2.2 前端代码
- 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script>
</head>
<body>
<h1>首页</h1>
<div id="root">
<button @click="experimentOne">实验一</button>
<p>{{msg}}</p>
</div>
</body>
<script>
var qs = Qs
new Vue({
el: '#root',
data: {
msg: ''
},
methods: {
experimentOne: function () {
// 请求:发送普通请求参数
// 响应:普通文本
axios.post("/ajax/experiment/one", qs.stringify({
userName: "tom",
password: "123456"
}), {
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
}).then(result => {
// 接收服务器端响应的数据
this.msg = result.data
})
}
}
})
</script>
</html>
2.2 后端代码
- 示例:
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 使用 @ResponseBody 告诉 SpringMVC,将当前方法的返回值作为响应体,不要再去寻找视图。
// 方法返回值的类型:
// 情况①:简单类型,SpringMVC 会直接作为响应体数据。
// 情况②:复杂类型,SpringMVC 会将其转换为 JSON ,然后再作为响应体数据。需要 jackson 的支持。
@PostMapping("/one")
@ResponseBody
public String one(
// Ajax发过来的请求参数,对服务器端来说没有区别,还是和以前一样正常接收
@RequestParam("userName") String userName,
@RequestParam("password") String password) {
logger.info("DemoHandler.one的请求参数是{}和{}", userName, password);
// 服务器端给Ajax程序的响应数据通过handler方法的返回值提供
return "实验一";
}
}
第三章:实验二(⭐)
3.1 需求
- 请求:让整个请求体就是一个 JSON 数据。
- 响应:返回普通文本。
3.2 前端代码
- 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script> -->
</head>
<body>
<h1>首页</h1>
<div id="root">
<button @click="experimentOne">实验二</button>
</div>
</body>
<script>
new Vue({
el: '#root',
methods: {
experimentOne: function () {
// 请求:发送普通请求参数
// 响应:普通文本
axios.post("/ajax/experiment/two", {
"stuId": 55,
"stuName": "tom",
"subjectList": [
{
"subjectName": "java",
"subjectScore": 50.55
},
{
"subjectName": "php",
"subjectScore": 30.26
}
],
"teacherMap": {
"one": {
"teacherName": "tom",
"teacherAge": 23
},
"two": {
"teacherName": "jerry",
"teacherAge": 31
},
},
"school": {
"schoolId": 23,
"schoolName": "苏州大学"
}
}).then(result => {
// 接收服务器端响应的数据
console.log(result.data)
})
}
}
})
</script>
</html>
3.3 后端代码
- 导入依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
- 如果忘记导入这个依赖,会看到下面的错误信息:
- 关于 SpringMVC 和 jackson 包之间的关系,需要注意,当 SpringMVC 需要解析 JSON 数据的时候就需要 jackson 的支持,但是 SpringMVC 的 jar 包并没有依赖 jackson ,需要我们自己手动导入。
- 我们自己导入 jackson 的时候需要注意:SpringMVC 和 jackson 配置使用有版本的要求,二者任何一个版本太高或太低都不行。
- SpringMVC 解析 JSON 数据包括两个方向:
- 从 JSON 字符串到 Java 实体类。
- 从 Java 实体类到 JSON 字符串。
- 另外,如果导入了 jackson 的依赖,但是没有开启
<mvc:annotation-driven/>
,依然会返回上面的错误。 - 所以,SpringMVC 想要解析 JSON 数据需要两个方面的支持:
<mvc:annotation-driven/>
。- 引入 jackson 依赖。
- 还有一点,如果运行环境是 Tomcat 7,那么在 WEB 应用启动的时候会抛出下面的异常:
org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19
解决方案:升级为 Tomcat8 或更高的版本。
示例:
package com.github.fairy.era.mvc.entity;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-15 14:51
*/
public class Teacher {
private String teacherName;
private Integer teacherAge;
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
public Integer getTeacherAge() {
return teacherAge;
}
public void setTeacherAge(Integer teacherAge) {
this.teacherAge = teacherAge;
}
@Override
public String toString() {
return "Teacher{" +
"teacherName='" + teacherName + '\'' +
", teacherAge=" + teacherAge +
'}';
}
}
package com.github.fairy.era.mvc.entity;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-15 14:50
*/
public class Subject {
private String subjectName;
private Double subjectScore;
public String getSubjectName() {
return subjectName;
}
public void setSubjectName(String subjectName) {
this.subjectName = subjectName;
}
public Double getSubjectScore() {
return subjectScore;
}
public void setSubjectScore(Double subjectScore) {
this.subjectScore = subjectScore;
}
@Override
public String toString() {
return "Subject{" +
"subjectName='" + subjectName + '\'' +
", subjectScore=" + subjectScore +
'}';
}
}
package com.github.fairy.era.mvc.entity;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-15 14:53
*/
public class School {
private Integer schoolId;
private String schoolName;
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
@Override
public String toString() {
return "School{" +
"schoolId=" + schoolId +
", schoolName='" + schoolName + '\'' +
'}';
}
}
package com.github.fairy.era.mvc.entity;
import java.util.List;
import java.util.Map;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-15 14:50
*/
public class Student {
private Integer stuId;
private String stuName;
private List<Subject> subjectList;
private Map<String, Teacher> teacherMap;
private School School;
public Integer getStuId() {
return stuId;
}
public void setStuId(Integer stuId) {
this.stuId = stuId;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public List<Subject> getSubjectList() {
return subjectList;
}
public void setSubjectList(List<Subject> subjectList) {
this.subjectList = subjectList;
}
public Map<String, Teacher> getTeacherMap() {
return teacherMap;
}
public void setTeacherMap(Map<String, Teacher> teacherMap) {
this.teacherMap = teacherMap;
}
public com.github.fairy.era.mvc.entity.School getSchool() {
return School;
}
public void setSchool(com.github.fairy.era.mvc.entity.School school) {
School = school;
}
@Override
public String toString() {
return "Student{" +
"stuId=" + stuId +
", stuName='" + stuName + '\'' +
", subjectList=" + subjectList +
", teacherMap=" + teacherMap +
", School=" + School +
'}';
}
}
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ResponseBody
@PostMapping("/two")
public String two(@RequestBody Student student) {
logger.info("student = " + student);
return "实验二";
}
}
3.4 @RequestBody 注解
- 使用 @RequestBody 注解的场景:请求体整个是一个 JSON 数据。
- Request Payload 翻译成中文大致可以说:请求负载。
第四章:实验三(⭐)
4.1 需求
- 请求:发送普通的请求参数,请求参数整体正好对应实体类。
- handler 方法:使用普通实体类接收请求参数。
- 响应:返回普通文本数据。
4.2 前端代码
- 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script>
</head>
<body>
<h1>首页</h1>
<div id="root">
<button @click="experimentOne">实验三</button>
<p>{{msg}}</p>
</div>
</body>
<script>
var qs = Qs
new Vue({
el: '#root',
data: {
msg: ''
},
methods: {
experimentOne: function () {
axios.post("/ajax/experiment/three", qs.stringify({
userName: "tom",
password: "123456"
}), {
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
}).then(result => {
// 接收服务器端响应的数据
this.msg = result.data
})
}
}
})
</script>
</html>
4.3 后端代码
- 示例:
package com.github.fairy.era.mvc.entity;
/**
* @author 许大仙
* @version 1.0
* @since 2021-11-15 15:50
*/
public class Employee {
private String userName;
private String 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 "Employee{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ResponseBody
@PostMapping("/three")
public String three(Employee employee) {
logger.info("employee = " + employee);
return "实验三";
}
}
4.4 常见错误
- 如果前端程序使用 Axios 发送请求参数,Content-Type 是 application/x-www-form-urlencoded,那么请求参数会附在 URL 地址后面,同时服务器端 handler 方法使用了 @RequestBody 注解,会在日志中看到下面的异常信息:
[org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]]
- 意思是 HTTP 的 MediaType 不支持。
第五章:实验四(⭐)
5.1 需求
- 请求:不需要发送任何数据。
- handler 方法:返回实体类。
- 响应:服务器端返回由实体类生成的 JSON 数据。
5.2 前端代码
- 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
</head>
<body>
<h1>首页</h1>
<div id="root">
<button @click="experiment">实验四</button>
<p>{{msg}}</p>
</div>
</body>
<script>
new Vue({
el: '#root',
data: {
msg: ''
},
methods: {
experiment: function () {
axios.post("/ajax/experiment/four", {}).then(result => {
// 接收服务器端响应的数据
this.msg = result.data
})
}
}
})
</script>
</html>
5.3 后端代码
- 示例:
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ResponseBody
@PostMapping("/four")
public Employee four() {
Employee employee = new Employee();
employee.setUserName("admin");
employee.setPassword("123456");
logger.info("employee = " + employee);
return employee;
}
}
第六章:@RestController 注解(⭐)
6.1 提取 @ResponseBody
- 如果类中每个方法都标记了 @ResponseBody 注解,那么这些注解即可以提取到类上。
- 示例:
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@Controller
@ResponseBody
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping("/four")
public Employee four() {
Employee employee = new Employee();
employee.setUserName("admin");
employee.setPassword("123456");
logger.info("employee = " + employee);
return employee;
}
}
6.2 合并
类上的 @ResponseBody 注解和 @Controller 注解可以合并为 @RestController 注解,换言之,使用了 @RestController 注解就相当于给类中的每个方法都加上 @ResponseBody 注解。
示例:
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
//@Controller
//@ResponseBody
@RestController
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping("/four")
public Employee four() {
Employee employee = new Employee();
employee.setUserName("admin");
employee.setPassword("123456");
logger.info("employee = " + employee);
return employee;
}
}
6.3 @RestController 注解源码
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
第七章:Spring MVC 4 版本响应体字符集设置
- 当返回响应体数据包含乱码的时候,可以在 @RequestMapping 注解或其衍生注解中设置 produces 属性给响应体设置内容类型。
package com.github.fairy.era.mvc.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@RestController
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping(value = "/demo", produces = "text/html;charset=UTF-8")
public String demo() {
return "我是谁,我在哪里?";
}
}
- 如果返回 JSON 数据时遇到乱码问题,那么内容类型应设置为:
application/json;charset=UTF-8
。
package com.github.fairy.era.mvc.handler;
import com.github.fairy.era.mvc.entity.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试
*
* @author 许大仙
* @version 1.0
* @since 2021-11-11 14:58
*/
@RestController
@RequestMapping("/experiment")
public class DemoHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostMapping(value = "/demo", produces = "application/json;charset=UTF-8")
public Employee demo() {
Employee employee = new Employee();
employee.setUserName("许大仙");
employee.setPassword("123456");
return employee;
}
}