⭐表示重要。

第一章:Ajax 回顾(⭐)

1.1 Ajax 概述

  • ① 服务器端渲染:

服务器端渲染.png

  • ② Ajax 渲染(局部更新):

Ajax渲染.png

1.2 前后端分离

  • 彻底舍弃服务器端渲染,数据全部通过 Ajax 方式以 JSON 格式来传递。

1.3 同步和异步

1.3.1 概述

  • Ajax 本身就是 Asynchronous JavaScript And XML 的缩写,直译为:异步的 JavaScript 和 XML 。在实际应用中 Ajax 指的是: 不刷新浏览器窗口不做页面跳转局部更新页面内容 的技术。
  • 同步异步 是相对的概念。

1.3.2 同步

  • 多个操作 按照顺序执行 ,前面的操作没有完成,后面的操作就必须 等待 ,所以同步操作通常是 串行 的。

同步.png

1.3.3 异步

  • 多个操作相继开始 并发执行 ,即使开始的先后顺序不同,但是由于它们各自是 在自己独立的进程或线程中 完成,所以 互不干扰,谁也不用等待谁

异步.png

1.4 Axios

  • 使用远程的 JavaScript 执行 Ajax 非常繁琐,所以一般会使用框架来完成,而 Axios 就是目前前端最流行的 Ajax 框架。
  • 官网
  • 使用 Axios 和使用 Vue 一样,导入对应的 *.js 文件即可。官方提供的 script 标签引入方式为:
  1. <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>

第二章:实验一(⭐)

2.1 需求

  • 请求:发送普通请求参数。
  • 响应:服务器端返回普通文本。

2.2 前端代码

  • 示例:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>首页</title>
  6. <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
  7. <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
  8. <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script>
  9. </head>
  10. <body>
  11. <h1>首页</h1>
  12. <div id="root">
  13. <button @click="experimentOne">实验一</button>
  14. <p>{{msg}}</p>
  15. </div>
  16. </body>
  17. <script>
  18. var qs = Qs
  19. new Vue({
  20. el: '#root',
  21. data: {
  22. msg: ''
  23. },
  24. methods: {
  25. experimentOne: function () {
  26. // 请求:发送普通请求参数
  27. // 响应:普通文本
  28. axios.post("/ajax/experiment/one", qs.stringify({
  29. userName: "tom",
  30. password: "123456"
  31. }), {
  32. headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  33. }).then(result => {
  34. // 接收服务器端响应的数据
  35. this.msg = result.data
  36. })
  37. }
  38. }
  39. })
  40. </script>
  41. </html>

2.2 后端代码

  • 示例:
  1. package com.github.fairy.era.mvc.handler;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.PostMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. import org.springframework.web.bind.annotation.ResponseBody;
  9. /**
  10. * 测试
  11. *
  12. * @author 许大仙
  13. * @version 1.0
  14. * @since 2021-11-11 14:58
  15. */
  16. @Controller
  17. @RequestMapping("/experiment")
  18. public class DemoHandler {
  19. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  20. // 使用 @ResponseBody 告诉 SpringMVC,将当前方法的返回值作为响应体,不要再去寻找视图。
  21. // 方法返回值的类型:
  22. // 情况①:简单类型,SpringMVC 会直接作为响应体数据。
  23. // 情况②:复杂类型,SpringMVC 会将其转换为 JSON ,然后再作为响应体数据。需要 jackson 的支持。
  24. @PostMapping("/one")
  25. @ResponseBody
  26. public String one(
  27. // Ajax发过来的请求参数,对服务器端来说没有区别,还是和以前一样正常接收
  28. @RequestParam("userName") String userName,
  29. @RequestParam("password") String password) {
  30. logger.info("DemoHandler.one的请求参数是{}和{}", userName, password);
  31. // 服务器端给Ajax程序的响应数据通过handler方法的返回值提供
  32. return "实验一";
  33. }
  34. }

第三章:实验二(⭐)

3.1 需求

  • 请求:让整个请求体就是一个 JSON 数据。
  • 响应:返回普通文本。

3.2 前端代码

  • 示例:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>首页</title>
  6. <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
  7. <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
  8. <!-- <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script> -->
  9. </head>
  10. <body>
  11. <h1>首页</h1>
  12. <div id="root">
  13. <button @click="experimentOne">实验二</button>
  14. </div>
  15. </body>
  16. <script>
  17. new Vue({
  18. el: '#root',
  19. methods: {
  20. experimentOne: function () {
  21. // 请求:发送普通请求参数
  22. // 响应:普通文本
  23. axios.post("/ajax/experiment/two", {
  24. "stuId": 55,
  25. "stuName": "tom",
  26. "subjectList": [
  27. {
  28. "subjectName": "java",
  29. "subjectScore": 50.55
  30. },
  31. {
  32. "subjectName": "php",
  33. "subjectScore": 30.26
  34. }
  35. ],
  36. "teacherMap": {
  37. "one": {
  38. "teacherName": "tom",
  39. "teacherAge": 23
  40. },
  41. "two": {
  42. "teacherName": "jerry",
  43. "teacherAge": 31
  44. },
  45. },
  46. "school": {
  47. "schoolId": 23,
  48. "schoolName": "苏州大学"
  49. }
  50. }).then(result => {
  51. // 接收服务器端响应的数据
  52. console.log(result.data)
  53. })
  54. }
  55. }
  56. })
  57. </script>
  58. </html>

3.3 后端代码

  • 导入依赖:
  1. <dependency>
  2. <groupId>com.fasterxml.jackson.core</groupId>
  3. <artifactId>jackson-databind</artifactId>
  4. <version>2.13.0</version>
  5. </dependency>
  • 如果忘记导入这个依赖,会看到下面的错误信息:

忘记导入jackson的依赖.png

  • 关于 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 应用启动的时候会抛出下面的异常:
  1. 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 数据。

@RequestBody 注解的使用场景.png

  • 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;
    }
}