SpringMVC - 1( 了解 + postman 工具 + 请求与响应 + Rest 风格 )

发布时间 2023-09-19 22:09:55作者: 朱呀朱~

SpringMVC

  • SpringMVC 是隶属于 Spring 框架的一部分,主要是用来进行 Web 开发,是对 Servlet 进行了封装
  • SpringMVC 是处于 Web 层的框架,所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端
  • REST 是一种软件架构风格,可以降低开发的复杂性,提高系统的可伸缩性,后期的应用也是非常广泛

SpringMVC 概述

  • SpringMVC:
    1. 一种表现层框架技术
    2. 用于进行表现层功能开发
  • 浏览器发送一个请求给后端服务器,后端服务器原来是使用 Servlet 来接收请求和数据

    • servlet 继承 extends HttpServlet 并覆盖 doGet 和 doPost
    • 如果所有的处理都交给 Servlet 来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利
  • 将后端服务器 Servlet 拆分成三层,分别是 web、service 和 dao

    • web 层主要由 servlet 来处理,负责页面请求和数据的收集以及响应结果给前端
    • service 层主要负责业务逻辑的处理
    • dao 层主要负责数据的增删改查操作
  • servlet 处理请求和数据的时候,存在的问题是一个 servlet 只能处理一个请求

  • 针对 web 层进行了优化,采用了 MVC 设计模式,将其设计为 controller、view 和 Model

    • controller 负责请求和数据的接收,接收后将其转发给 service 业务处理
    • service 根据需要会调用 dao 对数据进行增删改查
    • dao 把数据处理完后将结果交给 service,service 再交给 controller
    • controller 根据需求组装成 Model 和 View,Model 和 View 组合起来生成页面转发给前端浏览器
    • 这样做的好处就是 controller 可以处理多个请求,并对请求进行分发,执行不同的业务操作
  • SpringMVC 主要负责的就是

    • controller 如何接收请求和数据
    • 如何将请求和数据转发给业务层
    • 如何将响应数据转换成 json 发回到前端
  • SpringMVC 进行一个定义:
    • 一种基于 Java 实现 MVC 模型的轻量级 Web 框架
  • 优点

    • 使用简单、开发便捷 ( 相比于 Servlet )
    • 灵活性强

SpringMVC 入门案例

  • SpringMVC的具体的实现流程:
  1. 创建 web 工程 ( Maven 结构 )

  2. 设置 tomcat 服务器,加载 web 工程 ( tomcat 插件 )

  3. 导入坐标 ( SpringMVC + Servlet )

  4. 定义处理请求的功能类 ( UserController )

  5. 设置请求映射 ( 配置映射关系 )

  6. 将 SpringMVC 设定加载到 Tomcat 容器中

案例制作

  1. 创建 Maven 项目
  • 打开 IDEA,创建一个新的 web 项目
  1. 补全目录结构
  • 因为使用骨架创建的项目结构不完整,需要手动补全
  1. 导入 jar 包
  • 将 pom.xml 中添加 SpringMVC 需要的依赖
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  </dependencies>
 
  • 说明:servlet 的坐标为什么需要添加 <scope>provided</scope>

    • scope 是 maven 中 jar 包依赖作用范围的描述

    • 如果不设置默认是 compile 在在编译、运行、测试时均有效

    • 如果运行有效的话就会和后面 tomcat 中的 servlet-api 包发生冲突,导致启动报错

    • provided 代表的是该包只在编译和测试的时候用,运行的时候无效直接使用 tomcat 中的,就避免冲突

  1. 创建配置类
    • 一配置二扫描
@Configuration
@ComponentScan("com.qst.controller")
public class SpringMvcConfig {
}
  1. 创建 Controller 类
// 定义bean
@Controller
public class UserController {
    
    @RequestMapping("/save")
    @ResponseBody // 将返回的字符串整体作为响应的内容,即设置返回数据为json
    public String save(){
        System.out.println("user save ...");
        // 这里返回的字符串写成的就是json数据的格式
        return "{'module':'springmvc'}";
    }
}
  • 如果方法直接返回字符串,springmvc 会把字符串当成页面的名称在项目中进行查找返回,因为不存在对应返回值名称的页面,就会报 404 错误,找不到资源
  1. 使用配置类替换 web.xml

    • 启动的配置类,让其加载上述的 SpringMvcConfig 配置类

    • 将 web.xml 删除,换成 ServletContainersInitConfig 继承下述接口

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {

//加载springmvc配置类
    protected WebApplicationContext createServletApplicationContext() {
        //初始化WebApplicationContext对象
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        //加载指定配置类,就会加载mvc的配置类
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

// 设置由springmvc控制器处理的请求映射路径
    protected String[] getServletMappings() {
        // 这里就是所有请求都被拦截交给mvc处理的意思
        return new String[]{"/"};
    }

// 加载spring配置类
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}
  • AbstractDispatcherServletInitializer 类是 SpringMVC 提供的快速初始化 Web3.0 容器的抽象类,提供了三个接口方法供用户实现:
    • createServletApplicationContext 方法,创建 Servlet 容器时,加载 SpringMVC 对应的 bean 并放入 WebApplicationContext 对象范围中,而 WebApplicationContext 的作用范围为 ServletContext 范围,即整个 web 容器范围
    • getServletMappings 方法,设定 SpringMVC 对应的请求映射路径,即 SpringMVC 拦截哪些请求
    • createRootApplicationContext 方法,如果创建 Servlet 容器时需要加载非 SpringMVC 对应的 bean,使用当前方法进行,使用方式和 createServletApplicationContext 相同,用来加载 Spring 环境
    • createServletApplicationContext 用来加载 SpringMVC 环境

工作流程解析

  • 为了更好的使用 SpringMVC,我们将 SpringMVC 的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程和单次请求过程

启动服务器初始化过程

  1. 服务器启动,执行 ServletContainersInitConfig 类,初始化 web 容器

    • 功能类似于以前的 web.xml
  2. 执行 createServletApplicationContext 方法,创建了 WebApplicationContext 对象

    • 该方法加载下面第三步 SpringMVC 的配置类 SpringMvcConfig 来初始化 SpringMVC 的容器
  3. 加载 SpringMvcConfig 配置类

  4. 执行 @ComponentScan 加载对应的 bean

    • 扫描指定包及其子包下所有类上的注解,如 Controller 类上的 @Controller 注解
  5. 加载 UserController,每个 @RequestMapping 的名称对应一个具体的方法

    • 此时就建立了 /save 和 save 方法的对应关系
  6. 执行 getServletMappings 方法,设定 SpringMVC 拦截请求的路径规则

    • / 代表所拦截请求的路径规则,只有被拦截后才能交给 SpringMVC 来处理请求

单次请求过程

  1. 发送请求 http://localhost/save
  2. web 容器发现该请求满足 SpringMVC 拦截规则,将请求交给 SpringMVC 处理
  3. 解析请求路径 /save
  4. 由 /save 匹配执行对应的方法 save()
    • 上面的第五步已经将请求路径和方法建立了对应关系,通过 /save 就能找到对应的 save 方法
  5. 执行 save()
  6. 检测到有 @ResponseBody 直接将 save() 方法的返回值作为响应体返回给请求方

bean加载控制

  • config 目录存入的是配置类,写过的配置类有:

    • ServletContainersInitConfig
    • SpringConfig —— 业务 bean ( Service )、功能 bean ( DataSource,SqlSessionFactoryBean,MapperScannerConfigurer 等 )
    • SpringMvcConfig —— 加载表现层 bean,也就是 controller 包下的类
    • JdbcConfig
    • MybatisConfig
  • controller 目录存放的是 SpringMVC 的 controller 类

  • service 目录存放的是 service 接口和实现类

  • dao 目录存放的是 dao / Mapper 接口

问题分析

  • 想办法让 Spring 和 SpringMVC 分开加载各自的内容:

    • 在 SpringMVC 的配置类 SpringMvcConfig 中使用注解 @ComponentScan,我们只需要将其扫描范围设置到 controller 即可,如:@ComponentScan("com.qst.controller")

    • 在 Spring 的配置类 SpringConfig 中使用注解 @ComponentScan,当时扫描的范围中其实是已经包含了 controller,如:@ComponentScan(value="com.qst")

  • 从包结构来看的话,Spring 已经多把 SpringMVC 的 controller 类也给扫描到,因为功能不同,如何避免 Spring 错误加载到 SpringMVC 的 bean

思路分析

  • 即想办法在加载 Spring 控制的 bean 的时候排除掉 SpringMVC 控制的 bean
  • 具体该如何排除:

    • 方式一:Spring 加载的 bean 设定扫描范围为精准范围,例如 service 包、dao 包等

    • 方式二:Spring 加载的 bean 设定扫描范围为 com.qst,排除掉 controller 包中的 bean

    • 方式三:不区分 Spring 与 SpringMVC 的环境,加载到同一个环境中 ( 了解 )

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.itheima</groupId>
      <artifactId>springmvc_02_bean_load</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
    
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.16</version>
        </dependency>
    
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.6</version>
        </dependency>
    
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.47</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
    
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>1.3.0</version>
        </dependency>
      </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
        
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringMvcConfig.class);
            return ctx;
        }
        
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
        
        protected WebApplicationContext createRootApplicationContext() {
          return null;
        }
    }
    
    @Configuration
    @ComponentScan("com.qst.controller")
    public class SpringMvcConfig {
    }
    
    @Configuration
    @ComponentScan("com.qst")
    public class SpringConfig {
    }
    
    
  • 编写 Controller,Service,Dao,Domain 类

    @Controller
    public class UserController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'info':'springmvc'}";
        }
    }
    
    public interface UserService {
        public void save(User user);
    }
    
    @Service
    public class UserServiceImpl implements UserService {
        public void save(User user) {
            System.out.println("user service ...");
        }
    }
    
    public interface UserDao {
        @Insert("insert into tbl_user(name,age)values(#{name},#{age})")
        public void save(User user);
    }
    public class User {
        private Integer id;
        private String name;
        private Integer age;
        //setter..getter..toString略
    }
    

bean 加载控制

  • 方式一:修改Spring配置类,设定扫描范围为精准范围 ( 主要用到的方式 )

    @Configuration
    @ComponentScan({"com.qst.service","com.qst.dao"})
    public class SpringConfig {
    }
    
    • 上述只是通过例子说明可以精确指定让 Spring 扫描对应的包结构,真正在做开发的时候,因为 Dao 最终是交给 MapperScannerConfigurer 对象来进行扫描处理的,我们只需要将其扫描到 service 包即可

    • 但写上通用性好,而且规范不错,所以可以不去掉

  • 方式二:修改 Spring 配置类,设定扫描范围为 com.qst,排除掉 controller 包中的 bean

    @Configuration
    @ComponentScan(value="com.qst", 
            excludeFilters=@ComponentScan.Filter(
        	type = FilterType.ANNOTATION,
            classes = Controller.class
        )
    )
    public class SpringConfig {
    }
    
    • excludeFilters 属性:设置扫描加载 bean 时,排除的过滤规则

    • type 属性:设置排除规则,当前使用按照 bean 定义时的注解类型进行排除

      • ANNOTATION:按照注解排除 ( 只知道这一种即可 )
      • 了解:
        • ASSIGNABLE_TYPE:按照指定的类型过滤
        • ASPECTJ:按照 Aspectj 表达式排除,基本上不会用
        • REGEX:按照正则表达式排除
        • CUSTOM:按照自定义规则排除
    • classes 属性:设置排除的具体注解类,当前设置排除 @Controller 定义的 bean

  • 测试:

    public class App{
    	public static void main (String[] args){
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            System.out.println(ctx.getBean(UserController.class));
        }
    }
    
  • 如果 controller 被排除了,该方法执行就会报 bean 未被定义的错误

  • 注意:测试的时候,需要把 SpringMvcConfig 配置类上的 @ComponentScan 注解注释掉,否则不会报错,因为:

    • Spring 配置类扫描的包是 com.qst

    • SpringMVC 的配置类,SpringMvcConfig 上有一个 @Configuration 注解,也会被 Spring 扫描到

    • SpringMvcConfig 上又有一个 @ComponentScan,把 controller 类又给扫描进来了,演示的效果就出不来

  • 最后,有了 Spring 的配置类,要想在 tomcat 服务器启动将其加载,我们需要修改 ServletContainersInitConfig

    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
        //............
        protected WebApplicationContext createRootApplicationContext() {
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringConfig.class);
            return ctx;
        }
    }
    
  • 对于上述的配置方式,Spring 还提供了一种更简单的配置方式,可以不用再去创建 AnnotationConfigWebApplicationContext 对象,不用手动 register 对应的配置类:

    • 继承新的类:AbstractAnnotationConfigDispatcherServletInitializer,只需要告诉配置文件的名字即可
    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{SpringConfig.class};
        }
     // 注意方法名与配置文件的对应,勿混淆
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    }
    

PostMan 工具的使用

PostMan 简介

  • 代码编写完后,我们要想测试,只需要打开浏览器直接输入地址发送请求即可。发送的是 GET 请求可以直接使用浏览器,但是如果要发送的是 POST 请求呢,如果要求发送的是 post 请求,我们就得准备页面在页面上准备 form 表单,测试起来比较麻烦,所以我们就需要借助一些第三方工具,如 PostMan:
  • PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件

    1630463382386

  • 作用:常用于进行接口测试

  • 特征

    • 简单
    • 实用
    • 美观
    • 大方

PostMan 安装

  • 安装完成后,如果需要注册,可以按照提示进行注册,如果底部有跳过测试的链接也可以点击跳过注册

  • 创建 WorkSpace 工作空间

    image-20230905164310898

  • 发送请求

    image-20230905211542738

保存当前请求

1630464783034

  • 注意:第一次请求需要创建一个新的目录,后面就不需要创建新目录,直接保存到已经创建好的目录即可。

请求与响应

设置请求映射路径

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <!-- ....... -->
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
      </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    }
    
    @Configuration
    @ComponentScan("com.qst.controller")
    public class SpringMvcConfig {
    }
    
    
  • 编写 BookController 和 UserController

    @Controller
    public class UserController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'module':'user save'}";
        }
        
        @RequestMapping("/delete")
        @ResponseBody
        public String save(){
            System.out.println("user delete ...");
            return "{'module':'user delete'}";
        }
    }
    
    @Controller
    public class BookController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("book save ...");
            return "{'module':'book save'}";
        }
    }
    
  • 最终创建好的项目结构如下:

  • 把环境准备好后,启动 Tomcat 服务器,后台会报错,即

    • UserController 有一个 save 方法,访问路径为 http://localhost/save

    • BookController 也有一个 save 方法,访问路径为 http://localhost/save

  • 不知道到底是访问哪个

问题分析

  • 团队多人开发,每人设置不同的请求路径,冲突问题解决方法:

  • 对于 Book 模块的 save,将其访问路径设置 http://localhost/book/save

  • 对于 User 模块的 save,将其访问路径设置 http://localhost/user/save

设置映射路径

修改 Controller
@Controller
public class UserController {

    @RequestMapping("/user/save")
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'module':'user save'}";
    }
    
    @RequestMapping("/user/delete")
    @ResponseBody
    public String save(){
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }
}

@Controller
public class BookController {

    @RequestMapping("/book/save")
    @ResponseBody
    public String save(){
        System.out.println("book save ...");
        return "{'module':'book save'}";
    }
}
  • 问题是解决了,但是每个方法前面都需要进行修改,写起来比较麻烦而且还有很多重复代码,如果 /user 后期发生变化,所有的方法都需要改,耦合度太高
  • 所以优化:
优化路径配置
  • 加上请求路径的前缀:@RequestMapping ( 例如 user 部分 )

    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'module':'user save'}";
        }
        
        @RequestMapping("/delete")
        @ResponseBody
        public String save(){
            System.out.println("user delete ...");
            return "{'module':'user delete'}";
        }
    }
    
  • 注意:

    • 当类上和方法上都添加了 @RequestMapping 注解,前端发送请求的时候,要和两个注解的 value 值相加匹配才能访问到

    • @RequestMapping 注解 value 属性前面加不加 / 都可以

请求参数

  • 请求路径设置好后,只要确保页面发送请求地址和后台 Controller 类中配置的路径一致,就可以接收到前端的请求

    • 接收到请求后,如何接收页面传递的参数:
  • 关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:

    • GET 请求

    • POST 请求

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <!-- ............. -->
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
      </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    }
     // ................
    
    @Configuration
    @ComponentScan("com.zyz.controller")
    public class SpringMvcConfig {
    }
    
  • 编写 UserController

    @Controller
    public class UserController {
    
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(){
            return "{'module':'commonParam'}";
        }
    }
    
  • 编写模型类,User 和 Address

    public class Address {
        private String province;
        private String city;
        //setter...getter...略
    }
    public class User {
        private String name;
        private int age;
        //setter...getter...略
    }
    

最终创建好的项目结构如下:

参数传递

GET 发送单个参数
  • 发送请求与参数:
http://localhost:8080/commonParam?name=zyz
  • 接收参数:
    • 注意,参数列表内的接收 name 要与前端表单名对应
@Controller
public class UserController {

    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name){
        System.out.println("普通参数传递 name ==> "+name);
        return "{'module':'commonParam'}";
    }
}
GET 发送多个参数
  • 发送请求与参数:
http://localhost:8080/commonParam?name=zz&age=22
  • 接收参数:
@Controller
public class UserController {

    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name,int age){
        System.out.println("普通参数传递 name ==> "+name);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'commonParam'}";
    }
}
GET 请求中文乱码
  • 如果传递的参数中有中文,会发现接收到的参数会出现中文乱码问题

  • Tomcat8.5 以后的版本已经处理了中文乱码的问题

  • 但是 IDEA 中使用 Tomcat 插件的话,就需要修改 pom.xml 来解决 GET 请求中文乱码问题

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port><!--tomcat端口号-->
          <path>/</path> <!--虚拟目录-->
          <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
        </configuration>
      </plugin>
    </plugins>
  </build>
POST 发送参数
  • 发送请求与参数:

    • form-data 的话就是除了文本还能发送文件,这里选后面方框内的即可

    image-20230906100356414

  • 接收参数的 controller 和 GET 一致,不用做任何修改

POST 请求中文乱码
  • GET 的方法不能解决 POST ( tomcat 8.5 以上也不行 )
  • 解决方案:配置过滤器
package com.qst.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[] {filter};
//        如果是多个过滤器的话就是 return new Filter[] {filter,filter1,filter2......};
    }
}

  • CharacterEncodingFilter 是在 spring-web 包中,所以是用之前导入的对应的 jar 包
  • 两种方式的乱码处理不互通

五种类型参数传递

  • 研究一些比较复杂的参数传递,常见的参数种类有:

    • 普通参数

    • POJO 类型参数

    • 嵌套 POJO 类型参数

    • 数组类型参数

    • 集合类型参数

普通参数

  • 普通参数:url 地址传参,地址参数名与形参变量名相同,定义形参即可接收参数
  • 如果形参与地址参数名不一致:

  • 发送请求与参数:

http://localhost:8080/commonParam?name=朱哇啊&age=22
  • 后台接收参数:
public String commonParam(String userName, int age){
    System.out.println("普通参数传递 name ==> "+ userName);
  • 因为前端给的是 name,后台接收使用的是 userName,两个名称对不上,导致接收数据失败

  • 解决方案:使用 @RequestParam 注解

@Controller
public class UserController {

    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(@RequestParam("name") String userName, int age){
        System.out.println("普通参数传递 name ==> "+ userName);
        System.out.println("普通参数传递 age ==> "+ age);
        return "{'module':'commonParam'}";
    }
}
  • 注意:写上 @RequestParam 注解框架就不需要自己去解析注入,能提升框架处理性能

POJO 数据类型

  • 简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用 POJO 数据类型
  • POJO 参数:请求参数名与形参对象属性名相同,定义 POJO 类型形参即可接收参数
  • 此时需要使用前面准备好的 POJO 类,如 User:
public class User {
    private String name;
    private int age;
    //setter...getter...略
}
  • 发送请求和参数:

  • 后台接收参数:

//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(User user){
    System.out.println("普通参数传递 name ==> "+ user.getName());
    System.out.println("普通参数传递 age ==> "+ user.getAge());
    System.out.println(user);
    return "{'module':'pojo param'}";
  • 注意:
  • POJO 参数接收,前端 GET 和 POST 发送请求数据的方式不变
  • 请求参数 key 的名称要和 POJO 中属性的名称一致,否则无法封装

嵌套 POJO类型参数

  • 如果POJO对象中嵌套了其他的POJO类,如
public class Address {
    private String province;
    private String city;
    
    //setter...getter...略
}
public class User {
    private String name;
    private int age;
    //    不仅有简单属性,还有引用类型的属性
    private Address address;
    
    //setter...getter...略
}
  • 嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数
  • 发送请求和参数:

image-20230906102457172

  • 后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(User user){
    System.out.println(user);
    return "{'module':'pojo param'}";
}
  • 控制台输出:

    User{name='大阿达', age=28, address=Address{province='山东', city='青岛'}}
    
  • 注意:

    • 请求参数 key 的名称要和 POJO 中属性的名称一致,否则无法封装

数组类型参数

  • 如果前端需要获取用户的爱好,爱好绝大多数情况下都是多个,就用数组参数的形式

    • 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
  • 发送请求和参数:

    • 注意:参数名必须保持一致才能封装到一个数组中

image-20230906103343898

  • 后台接收参数:
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String[] hobby){
    System.out.println("数组参数传递 hobby ==> "+ Arrays.toString(hobby));
    return "{'module':'hobby param'}";
}
  • 控制台输出:
    • 数组参数传递 hobby ==> [吃饭, 睡觉, 打豆豆]

集合类型参数

  • 数组能接收多个值,那么集合呢

  • 发送请求和参数:

    image-20230906104115701

  • 后台接收参数:

//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(List<String> hobby){
    System.out.println("数组参数传递 hobby ==> "+ hobby);
    return "{'module':'list param'}";
}
  • 运行会报错:

    image-20230906104428724

    • 原因是:SpringMVC 将 List 看做是一个 POJO 实体类对象来处理,将其创建一个对象并准备把前端的数据 set 封装到对象中,但是 List 是一个接口无法创建对象,所以报错
  • 解决方案是:使用 @RequestParam 注解

//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(@RequestParam List<String> hobby){
    System.out.println("数组参数传递 hobby ==> "+ hobby);
    return "{'module':'list param'}";
}
  • 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系
  • 输出:
    • 集合List传递 hobby ==> [吃饭, 睡觉, 打豆豆]

JSON 数据传输参数

  • 现在比较流行的开发方式为异步调用

    • 前后台以异步方式进行交换,传输的数据使用的是 JSON,所以前端如果发送的是 JSON 数据,后端该如何接收
  • 对于 JSON 数据类型,我们常见的有三种:

    • json 普通数组(["value1","value2","value3",...])

    • json 对象({key1:value1,key2:value2,...})

    • json 对象数组([{key1:value1,...},{key2:value2,...}])

JSON普通数组

  1. pom.xml 添加依赖
    • SpringMVC 默认使用的是 jackson 来处理 json 的转换,所以需要在 pom.xml 添加 jackson 依赖
    • 注意在添加后一定要刷新 maven
<!--  json转换  -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.3</version>
</dependency>
  1. PostMan 发送 JSON 数据

    image-20230906110424418

  2. 开启 SpringMVC 注解支持

    • 在 SpringMVC 的配置类中开启 SpringMVC 的注解支持,这里面就包含了将 JSON 转换成对象的功能
    • 但此处还是报错 NoSuchMethodException,即还是在找 List 对象,所以下面还需要最后一步注解
@Configuration
@ComponentScan("com.qst.controller")
// 开启有json数据转化为mvc对象的功能,即开启json数据类型自动转换(但功能不仅限于此)
@EnableWebMvc
public class SpringMvcConfig {
}	
  1. 参数前添加 @RequestBody
    • @RequestParam 已经不好使了,因为信息是在请求体中,已经没有 param 了
    • 请求体:Body
//使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(@RequestBody List<String> hobby){
    System.out.println("集合List传递 hobby ==> "+ hobby);
    return "{'module':'list common for json param'}";
}
  1. 启动运行程序

    image-20230906140725313

    • 控制台输出:
      • 集合 List 传递 hobby ==> [66d, 哇]

JSON对象数据

  • 请求和数据的发送:
{
	"name":"zhuwazhu",
	"age":88
}
// 上面的请求信息因为没写address,所以控制台输出为address=null

// 下面的就是加入了嵌套的pojo的写法:
{
    "name":"zhuyazhu",
    "age":222,
    "address":{
        "province":"shandong",
        "city":"longquan"
    }
}
  • 后端接收数据:
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(@RequestBody User user){
    System.out.println("pojo(json)参数传递 user ==> "+ user);
    return "{'module':'pojo for json param'}";
}
  • 输出:

    • 第一个 json:

      pojo(json)参数传递 user ==> User{name='zhuyazhu', age=88, address=null}
      
    • 第二个 json:

      pojo(json)参数传递 user ==> User{name='zhuyazhu', age=222, address=Address{province='shandong', city='longquan'}}
      

JSON对象数组

  • 集合中保存多个 POJO 的实现

  • 请求和数据的发送:

    [
        {
            "name":"zhuwa",
            "age":20
        },
        {
            "name":"zhuyazhu",
            "age":30,
            "address":{
                "province":"shandong",
                "city":"longquan"
            }
        }
    ]
    
  • 后端接收数据:

    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(@RequestBody List<User> list){
        System.out.println("pojo(json)参数传递 user ==> "+ list);
        return "{'module':'pojo for json param'}";
    }
    
  • 启动程序访问测试的输出:

    pojo(json)参数传递 user ==> [User{name='zhuwa', age=20, address=null}, User{name='zhuyazhu', age=30, address=Address{province='shandong', city='longquan'}}]
    

小结

  • SpringMVC 接收 JSON 数据的实现步骤为:

    1. 导入 jackson 包

    2. 使用 PostMan 发送 JSON 数据

    3. 开启 SpringMVC 注解驱动,在配置类上添加 @EnableWebMvc 注解

    4. Controller 方法的参数前添加 @RequestBody 注解

@RequestBody 与 @RequestParam 区别

  • 区别

    • @RequestParam 用于接收 url 地址传参,暂改属性名,表单传参
    • @RequestBody 用于接收 json 数据,只要是 json 格式的
  • 应用

    • 后期开发中,发送 json 格式数据为主,@RequestBody 应用较广
    • 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数

日期类型参数传递

  • 日期类型比较特殊,因为对于日期的格式有 N 多中输入方式,比如:

    • 2088-08-18

    • 2088/08/18

    • 08/18/2088

    • ......

  • 多格式方法进行输入,统一格式获取

  1. 编写方法接收日期数据
    • 把参数设置为日期类型
// 注意是:import java.util.Date; —— util包下的

@RequestMapping("/dateParam")
@ResponseBody
public String commonParam(Date date){
    System.out.println("date ==> "+ date);
    return "{'module':'data param'}";
}
  1. 使用 PostMan 发送请求

    • 使用 PostMan 发送 GET 请求,并设置 date 参数

      http://localhost:8080/dateParam?date=2088/08/08
      

image-20230906200710077

  1. 输出结果:

    date ==> Sun Aug 08 00:00:00 CST 2088
    
  2. 更换日期格式

  • 2088-08-08 无法转换成日期类型,原因是 SpringMVC 默认支持的字符串转日期的格式为 yyyy/MM/dd,如若不是,SpringMVC 就无法进行格式转换

  • 解决方案:使用 @DateTimeFormat

@RequestMapping("/dateParam")
@ResponseBody
public String commonParam(Date date, @DateTimeFormat(pattern="yyyy-MM-dd") Date date1){
    System.out.println("下划线的默认格式 date ==> "+ date);
    System.out.println("短横线隔开格式的 date ==> "+ date1);
    return "{'module':'data param'}";
}
  1. 再次使用 PostMan 发送请求

    image-20230906202237152

  2. 控制台输出:

    下划线的默认格式 date ==> Sun Aug 08 00:00:00 CST 2088
    短横线隔开格式的 date ==> Sun Jun 06 00:00:00 CST 2066
    
  3. 携带时间的日期 ( 时分秒 )

@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
                        @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                        @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2)
    System.out.println("参数传递 date ==> "+date);
	System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
	System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
    return "{'module':'data param'}";
}
  1. PostMan 请求输入:

    http://localhost:8080/dateParam?date=2088/08/08&date1=2066-06-06&date2=2088/08/08 10:10:10&date3=2066-06-06 06:06:06
    

    image-20230906202950954

  2. 输出:

    下划线的默认格式 date ==> Sun Aug 08 00:00:00 CST 2088
    短横线隔开格式的 date ==> Sun Jun 06 00:00:00 CST 2066
    含有时分秒的默认格式 date ==> Sun Aug 08 10:10:10 CST 2088
    含有时分秒的横线隔开格式 date ==> Sun Jun 06 06:06:06 CST 2066
    

内部实现原理

  • 由于前后端的各种需求,所以需要做各种类型转换的处理,这个处理就是 SpringMVC 做的

  • SpringMVC 是如何实现的:SpringMVC 中提供了很多类型转换接口和实现类

  • 在框架中,有一些类型转换接口,其中有:

    • Converter 接口

      package org.springframework.core.convert.converter;
      // .............
      /**
      *	S: the source type
      *	T: the target type
      */
      @FunctionalInterface
      public interface Converter<S, T> {
          @Nullable
          T convert(S var1);
      }
      
  • 注意:Converter 所属的包为 org.springframework.core.convert.converter

  • Converter 接口的实现类,ctrl + H 查看各种实现类

    image-20230906204322662

    • 框架中有提供很多对应 Converter 接口的实现类,用来实现不同数据类型之间的转换
  • HttpMessageConverter 接口
    • 该接口是实现对象与 JSON 之间的转换工作
  • 注意:SpringMVC的配置类把 @EnableWebMvc 当做标配配置上去,不要省略

响应

  • SpringMVC 接收到请求和数据后,进行了一些处理

    • 当然这个处理可以是转发给 Service,Service 层再调用 Dao 层完成的

    • 比如:根据用户 ID 查询用户信息、查询用户列表、新增用户等

  • 对于响应,主要就包含两部分内容:

    • 响应页面

    • 响应数据

      • 文本数据
      • json数据
  • 因为异步调用是目前常用的主流方式,所以我们需要更关注的就是如何返回 JSON 数据

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //乱码处理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter = new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    
    // ......
    @Configuration
    @ComponentScan("com.qst.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 编写模型类 User

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
    
  • webapp 下创建 page.jsp

    <html>
    <body>
    <h2>Hello Spring MVC!</h2>
    </body>
    </html>
    
  • 编写 UserController

    @Controller
    public class UserController {
    
        
    }
    

响应页面

  1. 设置返回页面
@Controller
public class UserController {
    
    @RequestMapping("/toJumpPage")
    //注意
    //1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
    //2.方法需要返回String
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }
    
}
  1. 启动程序测试
  • 此处涉及到页面跳转,所以直接打开浏览器,输入

    http://localhost:8080/toJumpPage
    

返回文本数据 ( 了解 )

  1. 设置返回文本内容
@Controller
public class UserController {
    
   	@RequestMapping("/toText")
	//注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }
}
  1. 启动程序测试
  • 此处不涉及到页面跳转,使用 PostMan 进行测试,输入:

    http://localhost:8080/toText
    

image-20230906220408248

响应 JSON 数据

响应 POJO 对象
@Controller
public class UserController {
    
    @RequestMapping("/toJsonPOJO")
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("zhuya");
        user.setAge(30);
        return user;
    }
    
}
  • 返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的 json 数据,需要依赖 @ResponseBody 注解和 @EnableWebMvc 注解

  • 重新启动服务器,访问

    http://localhost:8080/toJsonPOJO
    

image-20230906220857509

响应 POJO 集合对象
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
    System.out.println("返回json集合数据");
    User user1 = new User();
    user1.setName("传智播客");
    user1.setAge(15);

    User user2 = new User();
    user2.setName("黑马程序员");
    user2.setAge(12);

    List<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);

    return userList;
}
  • 重新启动服务器,访问:

    http://localhost:8080/toJsonList
    

image-20230906221402446

说明:@ResponseBody

  • 该注解可以写在类上或者方法上
  • 写在类上就是该类下的所有方法都有 @ReponseBody 功能
  • 当方法上有 @ReponseBody 注解后:
    • 方法的返回值为字符串,会将其作为文本内容直接响应给前端
    • 方法的返回值为对象,会将对象转换成 JSON 响应给前端
  • 此处又使用到了类型转换,内部还是通过 Converter 接口的实现类完成的,所以 Converter 除了前面所说的功能外,它还可以实现:

    • 对象转 Json 数据 ( POJO —> json )

    • 集合转 Json 数据 ( Collection —> json )

Rest 风格

REST 简介

  • REST ( Representational State Transfer ),表现形式状态转换,它是一种软件架构风格

  • 当我们想表示一个网络资源的时候,可以使用两种方式:

    • 传统风格资源描述形式:
      • http://localhost/user/getById?id=1 查询 id 为 1 的用户信息
      • http://localhost/user/saveUser 保存用户信息
    • REST 风格描述形式:
      • http://localhost/user/1
      • http://localhost/user
  • 相较而言

    • 传统方式一般是一个请求 url 对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求 url 地址,就大概知道该 url 实现的是一个什么样的操作
    • 查看 REST 风格的描述,你会发现请求地址变的简单了,并且光看请求 URL 并不是很能猜出来该 URL 的具体功能
  • 所以 REST 的优点有:

    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作

    • 书写简化

  • 但这样的话,一个相同的 url 地址即可以是新增也可以是修改或者查询,那如何区分?

  • 按照 REST 风格访问资源时使用 —— 行为动作区分对资源进行了何种操作

    • http://localhost/users 查询全部用户信息 GET ( 查询 )
    • http://localhost/users/1 查询指定用户信息 GET ( 查询 )
    • http://localhost/users 添加用户信息 POST ( 新增 / 保存 )
    • http://localhost/users 修改用户信息 PUT ( 修改 / 更新 )
    • http://localhost/users/1 删除用户信息 DELETE ( 删除 )
  • MVC 暂只支持八种,常用的就是这四种

    image-20230907093454956

  • 按照不同的请求方式代表不同的操作类型

    • 发送 GET 请求是用来做查询

    • 发送 POST 请求是用来做新增

    • 发送 PUT 请求是用来做修改

    • 发送 DELETE 请求是用来做删除

  • 但是注意:

    • 上述行为是约定方式,约定不是规范,可以打破,所以称 REST 风格,而不是 REST 规范 —— 但实际上大多都是按上述规范来的
      • REST 提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
      • REST 中规定 GET / POST / PUT / DELETE 针对的是查询 / 新增 / 修改 / 删除,但是我们如果非要用 GET 请求做删除,这点在程序上运行是可以实现的
      • 但是如果绝大多数人都遵循这种风格,你写的代码让别人读起来就有点莫名其妙了
  • 描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts ......
  • RESTful:

    • 根据 REST 风格对资源进行访问称为 RESTful
  • 后期我们在进行开发的过程中,大多是都是遵从 REST 风格来访问我们的后台服务,所以可以说咱们以后都是基于 RESTful 来进行开发的

RESTful入门案例

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <!-- .............. -->
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
        </dependency>
      </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //乱码处理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter = new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    
    @Configuration
    @ComponentScan("com.qst.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 编写模型类 User

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
    
  • 编写 UserController

    @Controller
    public class UserController {
    	@RequestMapping("/save")
        @ResponseBody
        public String save(@RequestBody User user) {
            System.out.println("user save..."+user);
            return "{'module':'user save'}";
        }
    
        @RequestMapping("/delete")
        @ResponseBody
        public String delete(Integer id) {
            System.out.println("user delete..." + id);
            return "{'module':'user delete'}";
        }
    
        @RequestMapping("/update")
        @ResponseBody
        public String update(@RequestBody User user) {
            System.out.println("user update..." + user);
            return "{'module':'user update'}";
        }
    
        @RequestMapping("/getById")
        @ResponseBody
        public String getById(Integer id) {
            System.out.println("user getById..." + id);
            return "{'module':'user getById'}";
        }
    
        @RequestMapping("/findAll")
        @ResponseBody
        public String getAll() {
            System.out.println("user getAll...");
            return "{'module':'user getAll'}";
        }
    }
    

思路分析

  • 将之前的增删改查替换成 RESTful 的开发方式:
    1. 之前不同的请求有不同的路径,现在要将其修改为统一的请求路径
      • 修改前:新增 /save,修改 /update,删除 /delete ...
      • 修改后:增删改查 /users
    2. 根据 GET 查询、POST 新增、PUT 修改、DELETE 删除对方法的请求方式进行限定

修改 RESTful 风格

新增
@Controller
public class UserController {
	//设置当前请求方法为POST,表示REST风格中的添加操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save() {
        System.out.println("user save...");
        return "{'module':'user save'}";
    }
}
  • 将请求路径更改为 /users

    • 访问该方法使用 POST:http://localhost:8080/users
  • 使用 method 属性限定该方法的访问方式为 POST

    • 如果发送的不是 POST 请求,比如发送 GET 请求,则会报错
删除
@Controller
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
	@RequestMapping(value = "/users",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}
  • 将请求路径更改为 /users
    • 访问该方法使用 DELETE:http://localhost:8080/users
  • 访问成功,但是删除方法没有携带所要删除数据的 id,所以针对 RESTful 的开发,如何携带数据参数
传递路径参数
  • 前端发送请求的时候使用:http://localhost:8080/users/1,路径中的 1 就是我们想要传递的参数

  • 后端获取参数,需要做如下修改:

    • 修改 @RequestMapping 的 value 属性,将其中修改为 /users/{id},目的是和路径匹配

    • 在方法的形参前添加 @PathVariable 注解,表示来自于路径

@Controller
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}
  • 普通情况:

    image-20230907095858703

  • 方法形参的名称和路径 {} 中的值不一致:http://localhost:8080/users/1

    image-20230907100438907

  • 有多个参数需要传递:http://localhost:8080/users/1/zyz

    image-20230907100649331

修改
@Controller
public class UserController {
    //设置当前请求方法为PUT,表示REST风格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user) {
        System.out.println("user update..." + user);
        return "{'module':'user update'}";
    }
}
根据ID查询
@Controller
public class UserController {
    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }
}
查询所有
@Controller
public class UserController {
    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users" ,method = RequestMethod.GET)
    @ResponseBody
    public String getAll() {
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}
小结
  • RESTful 入门:

    • 设定 Http 请求动作 ( 动词 )

    • 设定请求参数 ( 路径变量 )

  • 关于接收参数,学过的三个注解 @RequestBody@RequestParam@PathVariable

  • 区别

    • @RequestParam 用于接收 url 地址传参或表单传参

      • 命名不一致

      • 作为集合

      • (@RequestParam("name") String userName)
        (@RequestParam List<String> hobby)
        
    • @RequestBody 用于接收 json 数据

      • 方法的返回值为字符串,会将其作为文本内容直接响应给前端
      • 方法的返回值为对象,会将对象转换成 JSON 响应给前端
    • @PathVariable 用于接收路径参数,使用 { 参数名称 } 描述路径参数

      • 与路径参数相互绑定
  • 应用

    • 后期开发中,发送请求参数超过 1 个时,以 json 格式为主,@RequestBody 应用较广
    • 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数
    • 采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用 @PathVariable 接收请求路径变量,通常用于传递 id 值

RESTful 快速开发

  • 需解决:( 这里用 Book 举例 )

    • 每个方法的 @RequestMapping 注解中都定义了访问路径 /books,重复性太高
    • 每个方法的 @RequestMapping 注解中都要使用 method 属性定义请求方式,重复性太高
    • 每个方法响应 json 都需要加上 @ResponseBody 注解,重复性太高
  • 解决后:

    @RestController //@Controller + @ReponseBody 两个注解结合
    
    @RequestMapping("/books")
    public class BookController {
        
    	//@RequestMapping(method = RequestMethod.POST)
        @PostMapping
        public String save(@RequestBody Book book){
            System.out.println("book save..." + book);
            return "{'module':'book save'}";
        }
    
        //@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
        @DeleteMapping("/{id}")
        public String delete(@PathVariable Integer id){
            System.out.println("book delete..." + id);
            return "{'module':'book delete'}";
        }
    
        //@RequestMapping(method = RequestMethod.PUT)
        @PutMapping
        public String update(@RequestBody Book book){
            System.out.println("book update..." + book);
            return "{'module':'book update'}";
        }
    
        //@RequestMapping(value = "/{id}",method = RequestMethod.GET)
        @GetMapping("/{id}")
        public String getById(@PathVariable Integer id){
            System.out.println("book getById..." + id);
            return "{'module':'book getById'}";
        }
    
        //@RequestMapping(method = RequestMethod.GET)
        @GetMapping
        public String getAll(){
            System.out.println("book getAll...");
            return "{'module':'book getAll'}";
        }
    }
    
  • @RestController

  • @GetMapping @PostMapping @PutMapping @DeleteMapping

RESTful 案例

需求分析

  • 需求一:图片列表查询,从后台返回数据,将数据展示在页面上

  • 需求二:新增图片,将新增图书的数据传递到后台,并在控制台打印

  • 重点是在 SpringMVC 中如何使用 RESTful 实现前后台交互

环境准备

  • 创建一个 Web 的 Maven 项目

  • pom.xml 添加 Spring 依赖

    <!-- ................. -->
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
        </dependency>
      </dependencies>
    
  • 创建对应的配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    
        //乱码处理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter = new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    
    @Configuration
    @ComponentScan("com.qst.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
    
    
  • 编写模型类 Book

    public class Book {
        private Integer id;
        private String type;
        private String name;
        private String description;
        //setter...getter...toString略
    }
    
  • 编写 BookController

    @Controller
    public class BookController {
    }
    

后台接口开发

  1. 编写 Controller 类并使用 RESTful 进行配置
@RestController
@RequestMapping("/books")
public class BookController {

    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save ==> "+ book);
        return "{'module':'book save success'}";
    }

 	@GetMapping
    public List<Book> getAll(){
        System.out.println("book getAll is running ...");
        List<Book> bookList = new ArrayList<Book>();

        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVC入门教程");
        book1.setDescription("小试牛刀");
        bookList.add(book1);

        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMVC实战教程");
        book2.setDescription("一代宗师");
        bookList.add(book2);

        Book book3 = new Book();
        book3.setType("计算机丛书");
        book3.setName("SpringMVC实战教程进阶");
        book3.setDescription("一代宗师呕心创作");
        bookList.add(book3);

        return bookList;
    }

}
  1. 使用 PostMan 进行测试

页面访问处理

  1. 拷贝静态页面

    • 将所需内容都拷贝到项目的 webapp 目录下
  2. 访问 pages 目录下的 books.html

    • 打开浏览器输入 http://localhost:8080/pages/books.html
  3. 会页面报错,因为 SpringMVC 拦截了所有的静态资源

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    
    • "/" 把所有的、包括 books.html 都拦下了
  4. SpringMVC 将静态资源进行放行

    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
        //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            //当发送 /pages/** 请求时,就访问 /pages 目录下的内容 
            //即访问/pages/任意 时候,从/pages目录下查找内容
            registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
            registry.addResourceHandler("/js/**").addResourceLocations("/js/");
            registry.addResourceHandler("/css/**").addResourceLocations("/css/");
            registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
        }
    }
    
  • 该配置类是在 config 目录下,SpringMVC 扫描的是 controller 包,所以该配置类还未生效,要想生效需要将 SpringMvcConfig 配置类进行修改

    @Configuration
    @ComponentScan({"com.qst.controller", "com.qst.config"})
    // 开启有json数据转化为mvc对象的功能,即开启json数据类型自动转换(但功能不仅限于此)
    @EnableWebMvc
    public class SpringMvcConfig {
    }