nwnusun

   ::  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装

SpringMVC是处于Web层的框架,所以其主要作用就是用来接收前段发过来的请求和数据,然后经过处理之后将处理结果响应给前端,所以如何处理情趣和响应是SpringMVC中非常重要的一块内容。

REST是一种软件架构风格,可以降低开发的复杂性,提高系统的可伸缩性,后期的应用也是非常广泛。

对于SpringMVC的学习,最终要达成的目标:

  1. 掌握基于SpringMVC获取请求参数和响应JSON数据操作
  2. 熟练应用基于REST风格的请求路径设置与参数传递
  3. 能根据实际业务建立前后端开发通信协议,并进行实现
  4. 基于SSM整合技术开发任意业务模块功能

SpringMVC概述

学习SpringMVC我们先来回顾下现在Web程序是如何做的,我们现在的Web程序大都基于MVC三层架构来实现的。

img

  • 如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极其不利
    • 所以将后端服务器Servlet拆分成三层,分别是web、service和dao
      • web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
      • service层主要负责业务逻辑的处理
      • dao层主要负责数据的增删改查操作
  • servlet处理请求和数据时,存在一个问题:一个servlet只能处理一个请求
  • 针对web层进行优化,采用MVC设计模式,将其设计为Controller、View和Model
    • controller负责请求和数据的接收,接收后将其转发给service进行业务处理
    • service根据需要会调用dao对数据进行增删改查
    • dao把数据处理完后,将结果交给serviceservice再交给controller
    • controller根据需求组装成ModelViewModelView组合起来生成页面,转发给前端浏览器
    • 这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作

随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。

img

  • 因为是异步调用,所以后端不需要返回View视图,将其去除
  • 前端如果通过异步调用的方式进行交互,后端就需要将返回的数据转换成JSON格式进行返回
  • SpringMVC主要负责的就是
    • controller如何接收请求和数据
    • 如何将请求和数据转发给业务层
    • 如何将响应数据转换成JSON发挥到前端
  • SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
    • 优点
      • 使用简单、开发快捷(相比较于Servlet)
      • 灵活性强

这里说的优点,我们通过下面的讲解与联系慢慢体会

SpringMVC入门案例

因为SpringMVC是一个Web框架,将来是要替换Servlet,所以先来回顾下以前Servlet是如何进行开发的?

  1. 创建web工程(Maven结构)
  2. 设置tomcat服务器,加载web工程(tomcat插件)
  3. 导入坐标(Servlet)
  4. 定义处理请求的功能类(UserServlet)
  5. 设置请求映射(配置映射关系)

SpringMVC的制作过程和上述流程几乎是一致的,具体的实现流程是什么?

  1. 创建web工程(Maven结构)
  2. 设置tomcat服务器,加载web工程(tomcat插件)
  3. 导入坐标(SpringMVC+Servlet)
  4. 定义处理请求的功能类(UserController)
  5. 设置请求映射(配置映射关系)
  6. 将SpringMVC设定加载到Tomcat容器中

案例制作

  • 步骤一:创建Maven项目

  • 步骤二:导入所需坐标(SpringMVC+Servlet)
    pom.xml中导入下面两个坐标

    <!--servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!--springmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  • 步骤三:创建SpringMVC控制器类(等同于我们前面做的Servlet)

    //定义Controller,使用@Controller定义Bean
    @Controller
    public class UserController {
        //设置当前访问路径,使用@RequestMapping
        @RequestMapping("/save")
        //设置当前对象的返回值类型
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'module':'SpringMVC'}";
        }
    }
    
  • 步骤四:初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的Bean

    //创建SpringMVC的配置文件,加载controller对应的bean
    @Configuration
    //
    @ComponentScan("com.blog.controller")
    public class SpringMvcConfig {
    
    }
    
  • 步骤五:初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求

    //定义一个servlet容器的配置类,在里面加载Spring的配置,继承AbstractDispatcherServletInitializer并重写其方法
    public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
        //加载SpringMvc容器配置
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(SpringMvcConfig.class);
            return context;
        }
        //设置哪些请求归SpringMvc处理
        protected String[] getServletMappings() {
            //所有请求都交由SpringMVC处理
            return new String[]{"/"};
        }
    
        //加载Spring容器配置
        protected WebApplicationContext createRootApplicationContext() {
            return null;
        }
    }
    
  • 步骤六:访问http://localhost:8080/save
    页面上成功出现{'info':'springmvc'},至此我们的SpringMVC入门案例就完成了

注意事项

  • SpringMVC是基于Spring的,在pom.xml只导入了spring-webmvcjar包的原因是它会自动依赖spring相关坐标

  • AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类

  • AbstractDispatcherServletInitializer
    

    提供了三个接口方法供用户实现

    • createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
    • getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
    • createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
  • createServletApplicationContext用来加载SpringMVC环境

  • createRootApplicationContext用来加载Spring环境

知识点1:@Controller

名称 @Controller
类型 类注解
位置 SpringMVC控制器类定义上方
作用 设定SpringMVC的核心控制器bean

知识点2:@RequestMapping

名称 @RequestMapping
类型 类注解或方法注解
位置 SpringMVC控制器类或方法定义上方
作用 设置当前控制器方法请求访问路径
相关属性 value(默认),请求访问路径

知识点3:@ResponseBody

名称 @ResponseBody
类型 类注解或方法注解
位置 SpringMVC控制器类或方法定义上方
作用 设置当前控制器方法响应内容为当前返回值,无需解析

入门案例小结

  • 一次性工作
    • 创建工程,设置服务器,加载工程
    • 导入坐标
    • 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
    • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
  • 多次工作
    • 定义处理请求的控制器类
    • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)

工作流程解析

这里将SpringMVC分为两个阶段来分析,分别是启动服务器初始化过程单次请求过程

启动服务器初始化过程

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

    • 功能类似于web.xml

      public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
      
          protected WebApplicationContext createServletApplicationContext() {
              AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
              context.register(SpringMvcConfig.class);
              return context;
          }
      
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
      
          protected WebApplicationContext createRootApplicationContext() {
              return null;
          }
      }
      
  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象

    • 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器

      protected WebApplicationContext createServletApplicationContext() {
          AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
          context.register(SpringMvcConfig.class);
          return context;
      }
      
  3. 加载SpringMvcConfig配置类

    @Configuration
    @ComponentScan("com.blog.controller")
    public class SpringMvcConfig {
    
    }
    
  4. 执行@ComponentScan加载对应的bean

    • 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法此时就建立了/save和save()方法的对应关系

    @Controller
    public class UserController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'module':'SpringMVC'}";
        }
    }
    
  6. 执行getServletMappingsS方法,设定SpringMVC拦截请求的路径规则/代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    

单次请求过程

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

Bean加载控制

问题分析

入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig的配置类,在之前学习Spring的时候也创建过一个配置类SpringConfig。这两个配置类都需要加载资源,那么它们分别都需要加载哪些内容?

我们先来回顾一下项目结构
com.blog下有configcontrollerservicedao这四个包

  • config

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

    • ServletContainersInitConfig
    • SpringConfig
    • SpringMvcConfig
    • JdbcConfig
    • MybatisConfig
  • controller目录存放的是SpringMVCcontroller

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

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

controller、service和dao这些类都需要被容器管理成bean对象,那么到底是该让SpringMVC加载还是让Spring加载呢?

  • SpringMVC

    控制的bean

    • 表现层bean,也就是controller包下的类
  • Spring

    控制的bean

    • 业务bean(Service)
    • 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)

分析清楚谁该管哪些bean以后,接下来要解决的问题是如何让SpringSpringMVC分开加载各自的内容。

思路分析

对于上面的问题,解决方案也比较简单

  • 加载Spring控制的bean的时候,排除掉SpringMVC控制的bean

那么具体该如何实现呢?

  • 方式一:Spring加载的bean设定扫描范围com.blog,排除掉controller包内的bean
  • 方式二:Spring加载的bean设定扫描范围为精确扫描,具体到service包,dao包等
  • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中(了解即可)

环境准备

在入门案例的基础上追加一些类来完成环境准备

  • 导入坐标

    <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.46</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>
    
  • com.blog.config下新建SpringConfig

    @Configuration
    @ComponentScan("com.blog")
    public class SpringConfig {
    }
    
  • 创建一张数据表

    create table tb_user(
        id int primary key auto_increment,
        name varchar(25),
        age int
    )S
    
  • 新建com.blog.servicecom.blog.daocom.blog.domain包,并编写如下几个类

    • User
    • UserDao
    • UserService
    • UserServiceImpl
    public class User {
        private Integer id;
        private String name;
        private Integer age;
    
        public User() {
        }
    
        public User(Integer id, String name, Integer age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
  • 编写App运行类

    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
            System.out.println(context.getBean(UserController.class));
        }
    }
    

设置bean加载控制

  • 运行App运行类,如果Spring配置类扫描到了UserController类,则会正常输出,否则将报错
    当前配置环境下,将正常输出

    com.blog.controller.UserController@8e0379d

  • 解决方案一:修改Spring配置类,设定扫描范围为精准范围

    @Configuration
    @ComponentScan({"com.blog.dao","com.blog.service"})
    public class SpringConfig {
    }
    

    再次运行App运行类,报错NoSuchBeanDefinitionException,说明Spring配置类没有扫描到UserController,目的达成

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

    @Configuration
    @ComponentScan(value = "com.blog",
        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

运行程序之前,我们还需要把SpringMvcConfig配置类上的@ComponentScan注解注释掉,否则不会报错,将正常输出

  • 出现问题的原因是
    • Spring配置类扫描的包是com.blog
    • SpringMVC的配置类,SpringMvcConfig上有一个@Configuration注解,也会被Spring扫描到
    • SpringMvcConfig上又有一个@ComponentScan,把controller类又给扫描进来了
    • 所以如果不把@ComponentScan注释掉,Spring配置类将Controller排除,但是因为扫描到SpringMVC的配置类,又将其加载回来,演示的效果就出不来
    • 解决方案,也简单,把SpringMVC的配置类移出Spring配置类的扫描范围即可。

运行程序,同样报错NoSuchBeanDefinitionException,目的达成

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

public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
    //加载SpringMvc配置
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringMvcConfig.class);
        return context;
    }
    //设置哪些请求归SpringMvc处理
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //加载Spring容器配置
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringConfig.class);
        return context;
    }
}

对于上面的ServletContainerInitConfig配置类,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext对象,不用手动register对应的配置类
我们改用继承它的子类AbstractAnnotationConfigDispatcherServletInitializer,然后重写三个方法即可

public class ServletContainerInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

知识点:@ComponentScan

名称 @ComponentScan
类型 类注解
位置 类定义上方
作用 设置spring配置类扫描路径,用于加载使用注解格式定义的bean
相关属性 excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的bean,需要指定类别(type)和具体项(classes)

PostMan工具的使用

PostMan使用

创建WorkSpace工作空间

img

发送请求

img

保存当前请求

img

请求与响应

前面我们已经完成了入门案例相关的知识学习,接来了我们就需要针对SpringMVC相关的知识点进行系统的学习。
SpringMVC是web层的框架,主要的作用是接收请求、接收数据、响应结果。
所以这部分是学习SpringMVC的重点内容,这里主要会讲解四部分内容:

  • 请求映射路径
  • 请求参数
  • 日期类型参数传递
  • 响应JSON数据

设置请求映射路径

环境准备

  • 创建一个Maven项目

  • 导入坐标
    这里暂时只导servletspringmvc的就行

    <!--servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!--springmvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  • 编写UserController和BookController

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

    创建SpringMvcConfig配置类

    @Configuration
    @ComponentScan("com.blog.controller")
    public class SpringMvcConfig {
    }
    
  • 创建ServletContainersInitConfig类,初始化web容器

    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[]{"/"};
        }
    }
    
  • 直接启动Tomcat服务器,会报错

    com.blog.controller.UserController#save()
    to { /save}: There is already ‘bookController’ bean method
    com.blog.controller.BookController#save() mapped.

从错误信息可以看出:

  • UserController有一个save方法,访问路径为http://localhost/save
  • BookController也有一个save方法,访问路径为http://localhost/save
  • 当访问http://localhost/save的时候,到底是访问UserController还是BookController?

问题分析

团队多人开发,每人设置不同的请求路径,冲突问题该如何解决?

  • 解决思路:为不同模块设置模块名作为请求路径前置
    • 对于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 delete(){
            System.out.println("user delete ..");
            return "{'module':'user delete'}";
        }
    }
    

    问题是解决了,但是每个方法前面都需要进行修改,写起来比较麻烦而且还有很多重复代码,如果/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 delete(){
            System.out.println("user delete ..");
            return "{'module':'user delete'}";
        }
    }
    
    @Controller
    @RequestMapping("/book")
    public class BookController {
    
        @RequestMapping("/save")s
        @ResponseBody
        public String save(){
            System.out.println("book save ..");
            return "{'module':'book module'}";
        }
    }
    

注意:

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

请求参数

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

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

  • GET
  • POST

针对于不同的请求前端如何发送,后端如何接收?

环境准备

  • 继续使用上面的环境即可,编写两个模型类User类和Address

    public class User {
        private String name;
        private int age;
    
        public User() {
        }
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    public class Address {
        private String province;
        private String city;
    
        public String getProvince() {
            return province;
        }
    
        public void setProvince(String province) {
            this.province = province;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public Address() {
        }
        
        public Address(String province, String city) {
            this.province = province;
            this.city = city;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "province='" + province + '\'' +
                    ", city='" + city + '\'' +
                    '}';
        }
    }
    
    • 同时修改一下UserController

      @Controller
      @RequestMapping("/user")
      public class UserController {
      
          @RequestMapping("/commonParam")
          @ResponseBody
          public String commonParam(String name){
              System.out.println("普通参数传递name --> " + name);
              return "{'module':'commonParam'}";
          }
      }
      

    参数传递

    • GET发送单个参数

      • 启动Tomcat服务器,发送请求与参数:http://localhost/commonParam?name=Jerry

      • 接收参数

        @Controller
        public class UserController {
        
            @RequestMapping("/commonParam")
            @ResponseBody
            public String commonParam(String name){
                System.out.println("普通参数传递name --> " + name);
                return "{'module':'commonParam'}";
            }
        }
        

        注意get请求的key需与commonParam中的形参名一致
        控制台输出普通参数传递name --> Jerry

    • GET发送多个参数

      • 发送请求与参数:localhost:8080/user/commonParam?name=Jerry&age=18

      • 接收参数

        @Controller
        @RequestMapping("/user")
        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'}";
            }
        }
        

        控制台输出

        普通参数传递name —> Jerry
        普通参数传递age —> 18

    • POST发送参数
      img

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

      @Controller
      @RequestMapping("/user")
      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'}";
          }
      }
      

      控制台输出如下

      普通参数传递name —> Tom
      普通参数传递age —> 19

    • POST请求中文乱码
      如果我们在发送post请求的时候,使用了中文,则会出现乱码

    • 解决方案:配置过滤器

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

      重启Tomcat服务器,并发送post请求,使用中文,控制台输出如下

      普通参数传递name —> 张三
      普通参数传递age —> 19

五种类型参数传递

前面我们已经能够使用GET或POST来发送请求和数据,所携带的数据都是比较简单的数据,接下来在这个基础上,我们来研究一些比较复杂的参数传递,常见的参数种类有

  • 普通类型
  • POJO类型参数
  • 嵌套POJO类型参数
  • 数组类型参数
  • 集合类型参数

下面我们就来挨个学习这五种类型参数如何发送,后台如何接收

普通类型

普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。

  • 发送请求与参数:localhost:8080/user/commonParam?name=Helsing&age=1024

  • 后台接收参数

    @Controller
    @RequestMapping("/user")
    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'}";
        }
    }
    
  • 控制台输出

    普通参数传递name —> Helsing
    普通参数传递age —> 1024

如果形参与地址参数名不一致该如何解决?例如地址参数名为username,而形参变量名为name,因为前端给的是username,后台接收使用的是name,两个名称对不上,会导致接收数据失败

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

    • 发送请求与参数:localhost:8080/user/commonParam?username=Helsing&age=1024

    • 后台接收参数

      @Controller
      @RequestMapping("/user")
      public class UserController {
      
          @RequestMapping("/commonParam")
          @ResponseBody
          public String commonParam(@RequestParam("username") String name, int age){
              System.out.println("普通参数传递name --> " + name);
              System.out.println("普通参数传递age --> " + age);
              return "{'module':'commonParam'}";
          }
      }
      

POJO数据类型

简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用POJO数据类型。

  • POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数

此时需要使用前面准备好的两个POJO类

public class User {
    private String name;
    private int age;
public class Address {
    private String province;
    private String city;
  • 发送请求和参数:localhost:8080/user/pojoParam?name=Helsing&age=1024

  • 后台接收参数

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

    POJO参数传递user —> User

  • 注意:

    • POJO参数接收,前端GET和POST发送请求数据的方式不变。
    • 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。

嵌套POJO类型

  • 环境准备
    我们先将之前写的Address类,嵌套在User类中

    public class User {
        private String name;
        private int age;
    
        private Address address;
    
    • 嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

    • 发送请求和参数:localhost:8080/user/pojoParam?name=Helsing&age=1024&address.province=Beijing&address.city=Beijing

    • 后台接收参数

      @RequestMapping("/pojoParam")
      @ResponseBody
      public String pojoParam(User user){
          System.out.println("POJO参数传递user --> " + user);
          return "{'module':'pojo param'}";
      }
      
    • 控制台输出如下

      POJO参数传递user —> User{name=’Helsing’, age=1024, address=Address{province=’Beijing’, city=’Beijing’}}

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

    数组类型

    举个简单的例子,如果前端需要获取用户的爱好,爱好绝大多数情况下都是多选,如何发送请求数据和接收数据呢?

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

    • 发送请求和参数:localhost:8080/user/arrayParam?hobbies=sing&hobbies=jump&hobbies=rap&hobbies=basketball

    • 后台接收参数

      @RequestMapping("/arrayParam")
      @ResponseBody
      public String arrayParam(String[] hobbies){
          System.out.println("数组参数传递user --> " + Arrays.toString(hobbies));
          return "{'module':'array param'}";
      }
      
    • 控制台输出如下

      数组参数传递user —> [sing, jump, rap, basketball]

    集合类型

    数组能接收多个值,那么集合是否也可以实现这个功能呢?

    • 发送请求和参数:localhost:8080/user/listParam?hobbies=sing&hobbies=jump&hobbies=rap&hobbies=basketball

    • 后台接收参数

      @RequestMapping("/listParam")
      @ResponseBody
      public String listParam(List hobbies) {
          System.out.println("集合参数传递user --> " + hobbies);
          return "{'module':'list param'}";
      }
      
    • 运行程序,报错

      java.lang.IllegalArgumentException: Cannot generate variable name for non-typed Collection parameter type
      
      • 错误原因:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
    • 解决方案是:使用@RequestParam注解

      @RequestMapping("/listParam")
      @ResponseBody
      public String listParam(@RequestParam List hobbies) {
          System.out.println("集合参数传递user --> " + hobbies);
          return "{'module':'list param'}";
      }
      
    • 控制台输出如下

      集合参数传递user —> [sing, jump, rap, basketball]

    知识点:@RequestParam

    名称 @RequestParam
    类型 形参注解
    位置 SpringMVC控制器方法形参定义前面
    作用 绑定请求参数与处理器方法形参间的关系
    相关参数 required:是否为必传参数 defaultValue:参数默认值

JSON数据传输参数

现在比较流行的开发方式为异步调用。前后台以异步方式进行交换,传输的数据使用的是JSON,所以前端如果发送的是JSON数据,后端该如何接收?

对于JSON数据类型,我们常见的有三种:

  • json普通数组([“value1”,”value2”,”value3”,…])
  • json对象({key1:value1,key2:value2,…})
  • json对象数组([{key1:value1,…},{key2:value2,…}])

下面我们就来学习以上三种数据类型,前端如何发送,后端如何接收

JSON普通数组

  • 步骤一:导入坐标

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    
  • 步骤二:开启SpringMVC注解支持 使用@EnableWebMvc,在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。

    @Configuration
    @ComponentScan("com.blog.controller")
    //开启json数据类型自动转换
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 步骤三:PostMan发送JSON数据
    img

  • 步骤四:后台接收参数,参数前添加@RequestBody
    使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据

    @RequestMapping("/jsonArrayParam")
    @ResponseBody
    public String jsonArrayParam(@RequestBody List<String> hobbies) {
        System.out.println("JSON数组参数传递hobbies --> " + hobbies);
        return "{'module':'json array param'}";
    }
    

    控制台输出如下

    JSON数组参数传递hobbies —> [唱, 跳, Rap, 篮球]

JSON对象

  • 请求和数据的发送:

    {
        "name":"菲茨罗伊",
        "age":"27",
        "address":{
            "city":"萨尔沃",
            "province":"外域"
        }
    }
    
  • 接收请求和参数

    @RequestMapping("/jsonPojoParam")
    @ResponseBody
    public String jsonPojoParam(@RequestBody User user) {
        System.out.println("JSON对象参数传递user --> " + user);
        return "{'module':'json pojo param'}";
    }
    

    控制台输出如下

    JSON对象参数传递user —> User{name=’菲茨罗伊’, age=27, address=Address{province=’外域’, city=’萨尔沃’}}

JSON对象数组

  • 发送请求和数据

    [
        {
            "name":"菲茨罗伊",
            "age":"27",
            "address":{
                "city":"萨尔沃",
                "province":"外域"
            }
        },
        {
            "name":"地平线",
            "age":"136",
            "address":{
                "city":"奥林匹斯",
                "province":"外域"
            }
        }
    ]
    
  • 接收请求和参数

    @RequestMapping("/jsonPojoListParam")
    @ResponseBody
    public String jsonPojoListParam(@RequestBody List<User> users) {
        System.out.println("JSON对象数组参数传递user --> " + users);
        return "{'module':'json pojo list param'}";
    }
    

    控制台输出如下

    JSON对象数组参数传递user —> [User{name=’菲茨罗伊’, age=27, address=Address{province=’外域’, city=’萨尔沃’}}, User{name=’地平线’, age=136, address=Address{province=’外域’, city=’奥林匹斯’}}]

小结

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

  1. 导入jackson包
  2. 开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
  3. 使用PostMan发送JSON数据
  4. Controller方法的参数前添加@RequestBody注解

知识点1:@EnableWebMvc

名称 @EnableWebMvc
类型 配置类注解
位置 SpringMVC配置类定义上方
作用 开启SpringMVC多项辅助功能

知识点2:@RequestBody

名称 @RequestBody
类型 形参注解
位置 SpringMVC控制器方法形参定义前面
作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

@RequestBody@RequestParam区别

  • 区别
    • @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
    • @RequestBody用于接收json数据【application/json】
  • 应用
    • 后期开发中,发送json格式数据为主,@RequestBody应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数

日期类型参数传递

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

  • 2088-08-18
  • 2088/08/18
  • 08/18/2088

针对这么多日期格式,SpringMVC该如何接收呢?下面我们来开始学习

  • 步骤一:编写方法接收日期数据

    @RequestMapping("/dateParam")
    @ResponseBody
    public String dateParam(Date date) {
        System.out.println("参数传递date --> " + date);
        return "{'module':'date param'}";
    }
    
  • 步骤二:启动Tomcat服务器

  • 步骤三:使用PostMan发送请求:localhost:8080/user/dateParam?date=2077/12/21

  • 步骤四:查看控制台,输出如下

    参数传递date —> Tue Dec 21 00:00:00 CST 2077

  • 步骤五:更换日期格式
    为了能更好的看到程序运行的结果,我们在方法中多添加一个日期参数

    @RequestMapping("/dateParam")
    @ResponseBody
    public String dateParam(Date date1,Date date2) {
        System.out.println("参数传递date1 --> " + date1);
        System.out.println("参数传递date2 --> " + date2);
        return "{'module':'date param'}";
    }
    

    使用PostMan发送请求,携带两个不同的日期格式,localhost:8080/user/dateParam?date1=2077/12/21&date2=1997-02-13
    发送请求和数据后,页面会报400,The request sent by the client was syntactically incorrect.
    错误的原因是将1997-02-13转换成日期类型的时候失败了,原因是SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd,而我们现在传递的不符合其默认格式,SpringMVC就无法进行格式转换,所以报错。
    解决方案也比较简单,需要使用@DateTimeFormat注解

    @RequestMapping("/dateParam")
    @ResponseBody
    public String dateParam(Date date1,@DateTimeFormat(pattern = "yyyy-MM-dd") Date date2) {
        System.out.println("参数传递date1 --> " + date1);
        System.out.println("参数传递date2 --> " + date2);
        return "{'module':'date param'}";
    }
    

    重新发送请求与数据,控制台输出如下,问题解决

    参数传递date1 —> Tue Dec 21 00:00:00 CST 2077
    参数传递date2 —> Thu Feb 13 00:00:00 CST 1997

  • 步骤六:携带具体时间的日期
    接下来我们再来发送一个携带具体时间的日期,如localhost:8080/user/dateParam?date1=2077/12/21&date2=1997-02-13&date3=2022/09/09 16:34:07,那么SpringMVC该怎么处理呢?
    继续修改UserController类,添加第三个参数,同时使用@DateTimeFormat来设置日期格式

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

    控制台输出如下

    参数传递date1 —> Tue Dec 21 00:00:00 CST 2077
    参数传递date2 —> Thu Feb 13 00:00:00 CST 1997
    参数传递date3 —> Fri Sep 09 16:34:07 CST 2022

知识点:@DateTimeFormat

名称 @DateTimeFormat
类型 形参注解
位置 SpringMVC控制器方法形参前面
作用 设定日期时间型数据格式
相关属性 pattern:指定日期时间格式字符串

内部实现原理

我们首先先来思考一个问题:

  • 前端传递字符串,后端使用日期Date接收
  • 前端传递JSON数据,后端使用对象接收
  • 前端传递字符串,后端使用Integer接收
  • 后台需要的数据类型有很多种
  • 在数据的传递过程中存在很多类型的转换

:谁来做这个类型转换?

  • :SpringMVC

:SpringMVC是如何实现类型转换的?

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

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

注意:Converter所属的包为org.springframework.core.convert.converter

/**
*    S: the source type
*    T: the target type
*/
@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    //该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
    T convert(S source);
}

到了源码页面我们按Ctrl+H可以来看看Converter接口的层次结构 这里给我们提供了很多对应Converter接口的实现类,用来实现不同数据类型之间的转换

img

  1. HttpMessageConverter接口
    该接口是实现对象与JSON之间的转换工作
    注意:需要在SpringMVC的配置类把@EnableWebMvc当做标配配置上去,不要省略

SSM整合

代码

https://gitee.com/lucky_sungit/spring_study.git

流程

  1. 创建工程

    • pom

          <dependencies>
              <dependency>
                  <groupId>javax.servlet</groupId>
                  <artifactId>javax.servlet-api</artifactId>
                  <version>4.0.1</version>
              </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.2.8</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>8.0.22</version>
              </dependency>
              <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis-spring</artifactId>
                  <version>1.3.0</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-jdbc</artifactId>
                  <version>5.2.10.RELEASE</version>
              </dependency>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.16</version>
              </dependency>
              <dependency>
                  <groupId>com.fasterxml.jackson.core</groupId>
                  <artifactId>jackson-databind</artifactId>
                  <version>2.14.0</version>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.12</version>
                  <scope>test</scope>
              </dependency>
              <dependency>
                  <groupId>org.springframework</groupId>
                  <artifactId>spring-test</artifactId>
                  <version>5.2.10.RELEASE</version>
              </dependency>
          </dependencies>
      
  2. SSM整合

    • spring

      • SpringConfig

        @Configuration
        @ComponentScan({"com.nwnu.sun.service", "com.nwnu.sun.dao"})
        //@ComponentScan(value = "com.nwnu.sun",
        //        excludeFilters = @ComponentScan.Filter(
        //                type = FilterType.ANNOTATION,
        //                classes = Controller.class
        //        )
        //)
        @Import({MybatisConfig.class, DatasourceConfig.class})
        public class SpringConfig {
        }
        
    • Mybatis

      • MybatisConfig

        public class MybatisConfig {
            @Bean
            public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
                SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
                sqlSessionFactoryBean.setTypeAliasesPackage("com.nwnu.sun.dto");
                sqlSessionFactoryBean.setDataSource(dataSource);
                return sqlSessionFactoryBean;
            }
        
            @Bean
            public MapperScannerConfigurer mapperScannerConfigurer() {
                MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
                mapperScannerConfigurer.setBasePackage("com.nwnu.sun.dao");
                return mapperScannerConfigurer;
            }
        }
        
      • JdbcConfig

        @PropertySource("classpath:jdbc.properties")
        public class DatasourceConfig {
        
            @Value("${jdbc.driver}")
            private String driver;
            @Value("${jdbc.url}")
            private String url;
            @Value("${jdbc.username}")
            private String username;
            @Value("${jdbc.password}")
            private String password;
        
            @Bean
            public DataSource dataSource() {
                DruidDataSource dataSource = new DruidDataSource();
                dataSource.setDriverClassName(driver);
                dataSource.setUrl(url);
                dataSource.setUsername(username);
                dataSource.setPassword(password);
                return dataSource;
            }
        
            @Bean
            public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
                DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
                transactionManager.setDataSource(dataSource);
                return transactionManager;
            }
        }
        
      • jdbc.properties

        jdbc.driver=com.mysql.jdbc.Driver
        jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false&serverTimezone=Asia/Shanghai
        jdbc.username=root
        jdbc.password=123456
        
    • springmvc

      • ServletConfig

        public class ServletcontainerInit extends AbstractAnnotationConfigDispatcherServletInitializer {
        
            @Override
            protected Class<?>[] getRootConfigClasses() {
                return new Class[]{SpringConfig.class};//加载spring
            }
        
            @Override
            protected Class<?>[] getServletConfigClasses() {
                return new Class[]{SpringMvc.class};//加载springmvc容器
            }
        
            @Override
            protected String[] getServletMappings() {
                return new String[]{"/"};//设置映射
            }
        
            //处理乱码问题
            protected Filter[] getServletFilers() {
                CharacterEncodingFilter filter = new CharacterEncodingFilter();
                filter.setEncoding("UTF-8");
                return new Filter[]{filter};
            }
        }
        
      • SpringMvcConfig

        @Configuration
        @ComponentScan({"com.nwnu.sun.controller"})
        @EnableWebMvc
        public class SpringMvc implements WebMvcConfigurer {
            @Autowired
            private MyInterpetor myInterpetor;
        
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(myInterpetor).addPathPatterns("/books/action/all");
            }
        
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/page/**").addResourceLocations("/page");
            }
        }
        
  3. 功能模块

    • 表与实体

      @AllArgsConstructor
      @NoArgsConstructor
      @Data
      public class Book {
          private Integer id;
          private String type;
          private String name;
          private String description;
      }
      
      
    • dao(接口+自动代理)

      @Mapper
      public interface BookDao {
          @Select("select * from book where id = #{id}")
          public Book queryById(Integer id);
      
          @Insert("insert into book (type,name,description) values(#{type},#{name},#{description})")
          public Boolean insertBook(Book book);
      
          @Update("update set book type=#{type},#{name},#{description}")
          public Boolean updateBook(Book book);
      
          @Delete("delete from book where id = #{id}")
          public Boolean deleteById(Integer id);
      
          @Select("select * from book")
          public List<Book> queryAll();
      }
      
    • service(接口+实现类)

      @Transactional
      public interface BookService {
          public Book queryById(Integer id);
      
          public Boolean insertBook(Book book);
      
          public Boolean updateBook(Book book);
      
          public Boolean deleteById(Integer id);
      
          public List<Book> queryAll();
      }
      
      @Service
      public class BookServiceImpl implements BookService {
          @Autowired
          private BookDao bookDao;
      
          @Override
          public Book queryById(Integer id) {
              return bookDao.queryById(id);
          }
      
          @Override
          public Boolean insertBook(Book book) {
              return bookDao.insertBook(book);
          }
      
          @Override
          public Boolean updateBook(Book book) {
              return bookDao.updateBook(book);
          }
      
          @Override
          public Boolean deleteById(Integer id) {
              return bookDao.deleteById(id);
          }
      
          @Override
          public List<Book> queryAll() {
              return bookDao.queryAll();
          }
      }
      
      • 业务接口测试(整合Junit)

        //这是在MVC配置类中存在 @EnableWebMvc 该注解导致报错,在使用Junit测试时需去掉该注解即可。
        @RunWith(SpringRunner.class)
        @ContextConfiguration(classes = SpringConfig.class)
        public class BookControllerTest {
        
            @Autowired
            private BookService bookService;
        
            @Test
            public void quertAll() {
                List<Book> books = bookService.queryAll();
                System.out.println(books.toString());
            }
            @Test
            public  void insert(){
                Book book = new Book();
                book.setName("化学书");
                book.setType("化学");
                book.setDescription("这是一本化学书");
                Boolean aBoolean = bookService.insertBook(book);
                System.out.println(aBoolean);
            }
            @Test
            public void delete(){
                Boolean aBoolean = bookService.deleteById(2);
                System.out.println(aBoolean);
            }
        
        }
        
    • controller

      • 表现层接口测试(postman)

        @RestController
        @RequestMapping("/books")
        public class BookController {
            @Autowired
            private BookService bookService;
        
            @GetMapping("/action/{id}")
            public Result queryById(@PathVariable Integer id) {
                if (id != 2) {
                    throw new BusinessException(Resultcode.BUSEXEC.getCode(), Resultcode.BUSEXEC.getMessage());
                }
                Book book = bookService.queryById(id);
                return new Result(Resultcode.SUCCESS.getCode(), book, Resultcode.SUCCESS.getMessage());
            }
        
            @PutMapping("/action")
            public Result insertBook(@RequestBody Book book) {
                Boolean aBoolean = bookService.insertBook(book);
                return new Result(Resultcode.SUCCESS.getCode(), aBoolean, Resultcode.SUCCESS.getMessage());
            }
        
            @Delete("/action/{id}")
            public Result deleteByid(@PathVariable Integer id) {
                Boolean aBoolean = bookService.deleteById(id);
                return new Result(Resultcode.SUCCESS.getCode(), aBoolean, Resultcode.SUCCESS.getMessage());
            }
        
            @GetMapping("/action/all")
            public Result quertAll() {
                List<Book> books = bookService.queryAll();
                return new Result(Resultcode.SUCCESS.getCode(), books, Resultcode.SUCCESS.getMessage());
            }
        }
        

    前后端数据

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Result {
        private Long code;
        private Object object;
        private String message;
    
        public static Result success(Object o) {
            return new Result(Resultcode.SUCCESS.getCode(), o, Resultcode.SUCCESS.getMessage());
        }
        public  static Result failed(Object o){
            return new Result(Resultcode.ERROR.getCode(), o,Resultcode.ERROR.getMessage());
        }
    }
    
    public enum Resultcode {
        SUCCESS(10010, "成功"),
        ERROR(10011, "失败"),
        SYSEXCE(10012, "系统异常"),
        BUSEXEC(10013, "业务异常");
    
    
        private final long code;
        private final String message;
    
        Resultcode(long code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public long getCode() {
            return this.code;
        }
    
        public String getMessage() {
            return this.message;
        }
    }
    
    

异常处理器

  • 出现异常现象的常见位置与常见诱因如下:

    • 框架内部抛出的异常:因使用不合规导致
    • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
    • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作、导致索引异常等)
    • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型)
    • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放)
  • 思考

    1.各个层级出现异常,异常书写代码写在哪一层?

    2.表现层处理异常,每个方法中 单独书写,代码书写量巨大且意义不强,如何解决?AOP思想

  • 异常处理器

    • 集中的、统一的处理项目中出现的异常

      @RestControllerAdvice
      public class MyExceptionAdvice {
          @ExceptionHandler(Exception.class) //定义当前处理哪一种异常
          public Result doException(Exception e) {
              System.out.println("异常哪里跑");
              return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage());
          }
      }
      
    • 注解@RestControllerAdvice

      • 类型: 类注解

      • 位置:Rest风格开发的控制器增强类定义上方

      • 作用:为Rest风格开发的控制器类做增强

      • 范例

        @RestControllerAdvice
        public class ProjectExceptionAdvice{
        
        }
        
      • 说明:次注解自带@ResponseBody注解与@Component注解,具备对应的功能

    • 注解 @ExceptionHanadler

      • 类型:方法注解

      • 位置:专用于异常处理的控制器方法上方

      • 作用:设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行

      • 范例:

         @ExceptionHandler(Exception.class)
            public Result doException(Exception e) {
                System.out.println("异常哪里跑");
                return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage());
            }
        
      • 说明:此类方法可以更具处理的异常不同,制作多个方法分别处理对应的异常

    • 异常初期器处理效果对比

      image-20221123103702357

项目异常处理方案

  • 用户异常请求

  • 项目运行中无法度量的异常

    image-20221123104400523

  • 项目异常分类

    • 业务异常(BusinessException)
      • 规范的用户行为产生的异常
      • 不规范的用户行为操作产生的异常
    • 系统异常(SystemException)
      • 项目运行过程中可预计且无法避免的异常
    • 其他异常(Exception)
      • 编程人员未预期到的异常
  • 项目异常处理方案

    • 业务异常(BusinessException)
      • 发送对应消息传递给用户,提醒规范操作
    • 系统异常(SystemException)
      • 发送固定消息传递给用户,安抚用户
      • 发送特定消息给运维人员,提醒维护
      • 记录日志
    • 其他异常(Exception)
      • 发送固定消息传递给用户,安抚用户
      • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
      • 记录日志
  • 代码编写

    • 业务异常

      package com.nwnu.sun.exception;
      
      public class BusinessException extends RuntimeException {
          private long code; //定义异常编码
      
          public BusinessException(long code, String message) {
              super(message);
              this.code = code;
          }
      
          public BusinessException(long code, String message, Throwable cause) {
              super(message, cause);
              this.code = code;
          }
      
          public long getCode() {
              return code;
          }
      
          public String getMessage() {
              return super.getMessage();
          }
      }
      
    • 系统异常

      package com.nwnu.sun.exception;
      
      public class SystemException extends RuntimeException {
          private long code;
      
          public SystemException(long code, String message) {
              super(message);
              this.code = code;
          }
      
          public SystemException(long code, String message, Throwable cause) {
              super(message, cause);
              this.code = code;
          }
      
          public long getCode() {
              return code;
          }
      
          public String getMessage() {
              return super.getMessage();
          }
      }
      
    • 异常处理器

      package com.nwnu.sun.exception;
      
      import com.nwnu.sun.comm.Result;
      import com.nwnu.sun.comm.Resultcode;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      
      /**
       * @author nwnu
       * @date 2022/11/23 10:21
       * @description 全局控制器异常处理器
       */
      @RestControllerAdvice
      public class MyExceptionAdvice {
          @ExceptionHandler(Exception.class)
          public Result doException(Exception e) {
              System.out.println("异常哪里跑");
              //返回通用对象
              return new Result(Resultcode.ERROR.getCode(), e, Resultcode.ERROR.getMessage());
          }
      
          @ExceptionHandler(SystemException.class)
          public Result doSysException(SystemException se) {
              System.out.println("出现系统异常");
              System.out.println(se.getCode() + se.getMessage());
              return new Result(se.getCode(), null, se.getMessage());
          }
      
          @ExceptionHandler(BusinessException.class)
          public Result doBusinessException(BusinessException bs) {
              System.out.println("出现业务异常");
              return new Result(bs.getCode(), null, bs.getMessage());
          }
      }
      
  • 异常处理对比

    image-20221123114031715

资源处理器

servlet容器初始化拦截了 ‘/’ 路径,导致浏览器无法获取html资源

增加资源处理类

@Configuration
public class SpringmvcSupport implements WebMvcConfigurer {
    //静态资源展示
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //swagger 和 knife4j
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/page/**").addResourceLocations("/page");
    }
}

拦截器

  • 拦截器简介

    image-20221123120306763

  • 概念

    是一种动态拦截方法调用的机制,在SpringMvc中动态拦截控制器方法的执行

  • 作用:

    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原始方法的执行
  • 拦截器与过滤器区别

    • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术

    • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

       @Override
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
      

      在过滤器过滤后的请求,才能进行拦截器

入门案例

在上述整合案例中添加拦截器相关代码

  • 自定义拦截器 MyInterpetor,注意添加注解 @Component相关注解让sprng容器管理

    @Component
    public class MyInterpetor implements HandlerInterceptor {
        //原始操作之前运行的代码
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("prehandle");
            return true;
        }
    
        //原始操作之后
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("posthandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("after");
        }
    }
    
    
  • 定义配置类,继承 WebmMvcConfigurationSupport ,实现addInterceptor 方法

    注意添加@Configuration注解,并检查springmvc 的@ComponentScan 是否扫描到该配置

    @Configuration
    public class SpringmvcSupport extends WebMvcConfigurationSupport {
        @Autowired
        private MyInterpetor myInterpetor;
    
        //拦截器
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
         	//设置拦截路径,路径可以通过可变参数设置多个
            registry.addInterceptor(myInterpetor).addPathPatterns("/books/action/all");
        }
    }
    
  • postman 请求测试

    image-20221123151034022

检查日志是否打印

image-20221123151121378

简化配置

使用标准接口webMvcConfigurer简化开发(注意:侵入式较强)

@Configuration
@ComponentScan({"com.nwnu.sun.controller"})
@EnableWebMvc
public class SpringMvc implements WebMvcConfigurer {
    @Autowired
    private MyInterpetor myInterpetor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterpetor).addPathPatterns("/books/*");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/page/**").addResourceLocations("/page");
    }
}

执行流程

image-20221123152536746

参数

  • 前置处理

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("prehandle");
            return true;
        }
    
  • 参数

    • request: 请求对象
    • response: 响应对象
    • handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
  • 返回值

    • 返回值为false,被拦截的处理器将不执行
  • 完成后处理

      @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("after");
        }
    
  • 参数

    • ex:如果处理执行过程中出现异常对象,可以针对异常情况进行单独处理

多拦截器执行顺序

image-20221123154156409

  • preHandle:与配置顺序相同,必定运行
  • postHandle:与配置顺序相反,可能不运行
  • afterCompletion:与配置顺序相反,可能不运行
posted on 2022-11-23 16:33  匿名者nwnu  阅读(32)  评论(0编辑  收藏  举报