SpringMVC学习笔记 - 第二章 - SSM整合案例 - 技术整合、统一结果封装、统一异常处理、前后联调、拦截器

2023-07-29,,

【前置内容】Spring 学习笔记全系列传送门:

Spring学习笔记 - 第一章 - IoC(控制反转)、IoC容器、Bean的实例化与生命周期、DI(依赖注入)

Spring学习笔记 - 第二章 - 注解开发、配置管理第三方Bean、注解管理第三方Bean、Spring 整合 MyBatis 和 Junit 案例

Spring学习笔记 - 第三章 - AOP与Spring事务

SpingMVC 学习笔记全系列传送门:

SpringMVC学习笔记 - 第一章 - 工作流程、Bean加载控制、请求与响应(参数接收与内容返回)、RESTful
【本章】SpringMVC学习笔记 - 第二章 - SSM整合案例 - 技术整合、统一结果封装、统一异常处理、前后联调、拦截器

目录
1、SSM整合
1.1 流程分析
1.2 整合配置
1.3 功能模块开发
1.4 单元测试
1.5 PostMan 测试(细节不表)
2、统一结果封装(前后端数据通信协议)
2.1 表现层与前端数据传输协议定义
2.2 表现层与前端数据传输协议实现
2.2.1 思路分析
2.2.2 结果封装
3、统一异常处理
3.1 问题描述
3.2 异常处理器的使用
3.2.1 环境准备
3.2.2 使用步骤
3.2.3 相关知识点
3.2.3.1 @RestControllerAdvice
3.2.3.2 @ExceptionHandler
3.3 项目异常处理方案
3.3.1 异常分类
3.3.2 异常解决方案
3.3.3 异常解决方案的具体实现
4、前后台协议联调
4.1 环境准备
4.2 列表功能
4.3 添加功能
4.4 添加、修改、删除功能状态处理
4.5 修改功能
4.6 删除功能
5、拦截器
5.1 拦截器概念
5.2 拦截器入门案例
5.2.1 环境准备
5.2.2 拦截器开发
5.2.3 拦截器拦截规则
5.2.4 简化SpringMvcSupport的编写
5.3 拦截器参数
5.3.1 前置处理方法
5.3.2 后置处理方法
5.3.3 完成处理方法
5.4 拦截器链运行顺序

1、SSM整合

1.1 流程分析

1.2 整合配置

    创建 Maven - web 项目

    添加依赖

    <?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>priv.dandelion</groupId>
    <artifactId>08_ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging> <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency> <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency> <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency> <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
    </dependency> <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
    </dependency> <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    </dependency> <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
    </dependency> <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency> <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency> <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
    </dependency>
    </dependencies> <build>
    <plugins>
    <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.1</version>
    <configuration>
    <port>80</port>
    <path>/</path>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>

    创建项目包结构

    SpringConfig配置类

    @Configuration
    @ComponentScan({"priv.dandelion.service"})
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MybatisConfig.class})
    // 开启事务
    @EnableTransactionManagement
    public class SpringConfig {
    }

    JdbcConfig配置类

    public class JdbcConfig {
    @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;
    } // 事务控制管理器,数据源使用自动装配(由Spring管理)
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager ds = new DataSourceTransactionManager();
    ds.setDataSource(dataSource);
    return ds;
    }
    }

    Mybatis 配置类

    public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setTypeAliasesPackage("priv.dandelion.entity");
    return factoryBean;
    } @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
    MapperScannerConfigurer msc = new MapperScannerConfigurer();
    msc.setBasePackage("priv.dandelion.dao");
    return msc;
    }
    }

    jdbc.properties 配置文件

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm_db
    jdbc.username=root
    jdbc.password=123456

    SpringMvc 配置类

    @Configuration
    @ComponentScan("priv.dandelion.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }

    web 项目入口配置类

    public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    //加载Spring配置类
    protected Class<?>[] getRootConfigClasses() {
    return new Class[]{SpringConfig.class};
    }
    //加载SpringMVC配置类
    protected Class<?>[] getServletConfigClasses() {
    return new Class[]{SpringMvcConfig.class};
    }
    //设置SpringMVC请求地址拦截规则
    protected String[] getServletMappings() {
    return new String[]{"/"};
    } //设置post请求中文乱码过滤器
    @Override
    protected Filter[] getServletFilters() {
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("utf-8");
    return new Filter[]{filter};
    }
    }

1.3 功能模块开发

    SQL

    create database ssm_db character set utf8;
    use ssm_db;
    create table tbl_book(
    id int primary key auto_increment,
    type varchar(20),
    name varchar(50),
    description varchar(255)
    ) insert into `tbl_book`(`id`,`type`,`name`,`description`) values (1,'计算机理论','Spring实战 第五版','Spring入门经典教程,深入理解Spring原理技术内幕'),(2,'计算机理论','Spring 5核心原理与30个类手写实践','十年沉淀之作,手写Spring精华思想'),(3,'计算机理论','Spring 5设计模式','深入Spring源码刨析Spring源码中蕴含的10大设计模式'),(4,'计算机理论','Spring MVC+Mybatis开发从入门到项目实战','全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'),(5,'计算机理论','轻量级Java Web企业应用实战','源码级刨析Spring框架,适合已掌握Java基础的读者'),(6,'计算机理论','Java核心技术 卷Ⅰ 基础知识(原书第11版)','Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新'),(7,'计算机理论','深入理解Java虚拟机','5个纬度全面刨析JVM,大厂面试知识点全覆盖'),(8,'计算机理论','Java编程思想(第4版)','Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉'),(9,'计算机理论','零基础学Java(全彩版)','零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术'),(10,'市场营销','直播就这么做:主播高效沟通实战指南','李子柒、李佳奇、薇娅成长为网红的秘密都在书中'),(11,'市场营销','直播销讲实战一本通','和秋叶一起学系列网络营销书籍'),(12,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

    实体类

    public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
    //getter...setter...toString省略
    }

    Dao 接口

    public interface BookDao {
    
        //    @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
    @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    public void save(Book book); @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    public void update(Book book); @Delete("delete from tbl_book where id = #{id}")
    public void delete(Integer id); @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id); @Select("select * from tbl_book")
    public List<Book> getAll();
    }

    Service

    接口

    // 声明该类需要被事务管理
    @Transactional
    public interface BookService { public boolean save(Book book); public boolean update(Book book); public boolean delete(Integer id); public Book getById(Integer id); public List<Book> getAll();
    }

    实现类

    @Service
    public class BookServiceImpl implements BookService { @Autowired
    private BookDao bookDao; @Override
    public boolean save(Book book) {
    bookDao.save(book);
    return true;
    } @Override
    public boolean update(Book book) {
    bookDao.update(book);
    return true;
    } @Override
    public boolean delete(Integer id) {
    bookDao.delete(id);
    return true;
    } @Override
    public Book getById(Integer id) {
    return bookDao.getById(id);
    } @Override
    public List<Book> getAll() {
    return bookDao.getAll();
    }
    }

    Controller

    package priv.dandelion.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import priv.dandelion.dao.BookDao;
    import priv.dandelion.entity.Book;
    import priv.dandelion.service.BookService; import java.util.List; @Service
    public class BookServiceImpl implements BookService { @Autowired
    private BookDao bookDao; @Override
    public boolean save(Book book) {
    bookDao.save(book);
    return true;
    } @Override
    public boolean update(Book book) {
    bookDao.update(book);
    return true;
    } @Override
    public boolean delete(Integer id) {
    bookDao.delete(id);
    return true;
    } @Override
    public Book getById(Integer id) {
    return bookDao.getById(id);
    } @Override
    public List<Book> getAll() {
    return bookDao.getAll();
    }
    }

1.4 单元测试

    新建测试类

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class BookServiceTest {
    }

    注入 Service 类,编写测试方法

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class BookServiceTest { @Autowired
    private BookService bookService; @Test
    public void testGetById() {
    Book byId = bookService.getById(1);
    System.out.println(byId);
    } @Test
    public void testGetAll() {
    List<Book> all = bookService.getAll();
    System.out.println(all);
    }
    }

1.5 PostMan 测试(细节不表)

2、统一结果封装(前后端数据通信协议)

2.1 表现层与前端数据传输协议定义

目前程序返回的数据类型(返回的数据类型太多,未来可能会更加混乱)

Controller 增删改返回给前端的是 Boolean 类型的数据

true

Controller 查询单个返回给前端的是对象

{
"id":123,
"type":"xxx",
"name":"xxx",
"description":"xxx"
}

Controller 查询所有返回给前端的是对象的集合

[
{
"id":123,
"type":"xxx",
"name":"xxx",
"description":"xxx"
},
{
"id":124,
"type":"xxx",
"name":"xxx",
"description":"xxx"
}
]

将返回的结果数据统一的方案

为了封装返回的结果数据:创建结果模型类,封装数据到data属性中

便于知道什么内容是数据部分

为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中

多个相同的返回类型可能是不同操作,便于区分不同操作;另外,可以对code进行规定,如末位为0代表失败,为1代表成功

操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中

必要的错误信息

统一结果封装的格式

// 1.
{
"code":20031,
"data":true
} // 2.
{
"code":20040,
"data":null,
"msg":"查询失败"
} // 3.
{
code:20041,
"data":[
{
"id":123,
"type":"xxx",
"name":"xxx",
"description":"xxx"
},
{
"id":124,
"type":"xxx",
"name":"xxx",
"description":"xxx"
}
]
}

2.2 表现层与前端数据传输协议实现

2.2.1 思路分析

统一数据返回结果实体类

统一返回结果所需的状态码定义

2.2.2 结果封装

    创建 Result 类

    public class Result{
    private Object data;
    private Integer code;
    private String msg; // 构造、getter、setter略...
    }

    定义返回码 Code 类

    public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer DELETE_OK = 20021;
    public static final Integer UPDATE_OK = 20031;
    public static final Integer GET_OK = 20041; public static final Integer SAVE_ERR = 20010;
    public static final Integer DELETE_ERR = 20020;
    public static final Integer UPDATE_ERR = 20030;
    public static final Integer GET_ERR = 20040;
    }

    修改 Conteoller 类的返回值

    @RestController
    @RequestMapping("/books")
    public class BookController { @Autowired
    private BookService bookService; @PostMapping
    public Result save(@RequestBody Book book) {
    boolean flag = bookService.save(book);
    return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
    } @PutMapping
    public Result update(@RequestBody Book book) {
    boolean flag = bookService.update(book);
    return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
    } @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
    boolean flag = bookService.delete(id);
    return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
    } @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) { Book book = bookService.getById(id); Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
    String msg = book != null ? "" : "查询的数据不存在,请重试";
    return new Result(code, book, msg);
    } @GetMapping
    public Result getAll() { List<Book> all = bookService.getAll(); Integer code = all != null ? Code.GET_OK : Code.GET_ERR;
    String msg = all != null ? "" : "数据查询失败,请重试";
    return new Result(code, all, msg);
    }
    }

3、统一异常处理

3.1 问题描述

当出现异常时,响应500等错误代码,返回错误页面,前端获取到有效信息,无法处理

可能的异常的种类及其原因

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

统一处理方案

所有异常抛出到表现层进行处理

各级均可能出现异常,为保证统一处理,需要向上抛出,直至表现层统一处理
关于MVC模式与三层架构的关系可以参考:三层架构

异常分类

异常的种类有很多。对其分类以保证都能处理到

使用AOP

表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,可以使用AOP以提高耦合

3.2 异常处理器的使用

为实现统一的异常处理,Spring提供了异常处理器

3.2.1 环境准备

在表现层中创建统一异常处理类 ProjectExceptionAdvice
不一定非要写在表现层对应的 controller 包下,但是一定要保证 SpringMVC 控制类的包扫描配置能扫描到异常处理器类

3.2.2 使用步骤

    创建异常处理器类

    // 声明这个类用于Rest风格对应的统一异常处理器类
    @RestControllerAdvice
    public class ProjectExceptionAdvice { // 拦截异常
    @ExceptionHandler(Exception.class)
    public void doException(Exception ex) {
    System.out.println("捕获异常");
    }
    }

    异常处理器类返回结果到前端

    // 声明这个类用于Rest风格对应的统一异常处理
    @RestControllerAdvice
    public class ProjectExceptionAdvice { // 拦截异常
    @ExceptionHandler(Exception.class)
    // 修改返回值类型,向前端返回异常信息
    public Result doException(Exception ex) {
    String msg = "异常捕获";
    System.out.println(msg);
    return new Result(00000, null, msg);
    }
    }

3.2.3 相关知识点

3.2.3.1 @RestControllerAdvice
名称 @RestControllerAdvice
类型 类注解
位置 Rest风格开发的控制器增强类定义上方
作用 为Rest风格开发的控制器类做增强
说明 此注解自带@ResponseBody注解与@Component注解,具备对应的功能
3.2.3.2 @ExceptionHandler
名称 @ExceptionHandler
类型 方法注解
位置 专用于异常处理的控制器方法上方
作用 设置指定异常的处理方案,功能等同于控制器方法,
出现异常后终止原始控制器执行,并转入当前方法执行
说明 此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常

3.3 项目异常处理方案

3.3.1 异常分类

因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以在处理异常之前,需要对异常进行一个分类

业务异常(BusinessException)

规范的用户行为产生的异常:如用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入字符串
不规范的用户行为操作产生的异常:如故意传递错误数据

系统异常(SystemException)

项目运行过程中可预计但无法避免的异常:如数据库或服务器宕机

其他异常(Exception)

开发人员未预期到的异常:如用到的文件不存在

3.3.2 异常解决方案

业务异常(BusinessException)

向用户发送对应消息,提醒其规范操作

系统异常(SystemException)

向用户发送消息内容,告知当前系统状态及可选操作
发送特定消息给运维人员提醒维护
记录日志

其他异常(Exception)

向用户发送消息内容,告知当前系统状态
发送特定消息给开发人员,提醒维护
记录日志

3.3.3 异常解决方案的具体实现

    自定义异常类

    系统异常(SystemException)

    // 继承RuntimeException,可以不做处理自动上抛
    public class SystemException extends RuntimeException{ private Integer code; public SystemException(Integer code, String message) {
    super(message);
    this.code = code;
    } public SystemException(Integer code, String message, Throwable cause) {
    super(message, cause);
    this.code = code;
    } public Integer getCode() {
    return code;
    } public void setCode(Integer code) {
    this.code = code;
    }
    }

    业务异常(BusinessException)

    public class BusinessException extends RuntimeException{
    private Integer code; public BusinessException(Integer code, String message) {
    super(message);
    this.code = code;
    } public BusinessException(Integer code, String message, Throwable cause) {
    super(message, cause);
    this.code = code;
    } public Integer getCode() {
    return code;
    } public void setCode(Integer code) {
    this.code = code;
    }
    }

    将其他异常包成自定义异常(本案例在Service中进行)

    @Override
    public Book getById(Integer id) {
    // 将可能出现的异常进行包装,转换成自定义异常
    if (id <= 0) {
    throw new BusinessException(Code.BUSINESS_ERR, "数据不合法");
    } // 将可能出现的异常进行包装,转换成自定义异常
    try{
    int i = 1/0;
    return bookDao.getById(id);
    }catch (ArithmeticException ae){
    throw new SystemException(Code.SYSTEM_TIMEOUT_ERR, "服务器访问超时");
    }
    }

    处理器类中处理自定义异常

    原先的public Result doException(Exception ex){}仍然保留,用于处理其他异常

    此处为了方便新增了错误代码

    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKNOWN_ERR = 59999; public static final Integer BUSINESS_ERR = 60001;
    // 声明这个类用于Rest风格对应的统一异常处理
    @RestControllerAdvice
    public class ProjectExceptionAdvice { // 拦截异常
    @ExceptionHandler(SystemException.class)
    public Result doException(SystemException ex) {
    // 记录日志
    // 发送消息给运维
    // 邮件发送ex的对象给开发
    // 返回消息内容
    return new Result(ex.getCode(), null, ex.getMessage());
    } // 拦截异常
    @ExceptionHandler(BusinessException.class)
    public Result doException(BusinessException ex) {
    // 记录日志
    // 发送邮件给开发
    // 返回消息内容
    return new Result(ex.getCode(), null, ex.getMessage());
    } // 仍然保留,用于处理其他异常
    @ExceptionHandler(Exception.class)
    // 修改返回值类型,向前端返回异常信息
    public Result doException(Exception ex) {
    // 记录日志
    // 发送消息给运维
    // 邮件发送ex的对象给开发
    // 返回消息内容
    return new Result(Code.SYSTEM_UNKNOWN_ERR, null, "系统繁忙请稍后再试");
    }
    }

4、前后台协议联调

4.1 环境准备

页面准备

该模块使用到了 Axios 和 ElementUI

<!DOCTYPE html>

<html>

    <head>

        <!-- 页面meta -->

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title>SpringMVC案例</title>

        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">

        <!-- 引入样式 -->

        <link rel="stylesheet" href="../plugins/elementui/index.css">

        <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">

        <link rel="stylesheet" href="../css/style.css">

    </head>

    <body class="hold-transition">

        <div id="app">

            <div class="content-header">

                <h1>图书管理</h1>

            </div>

            <div class="app-container">

                <div class="box">

                    <div class="filter-container">

                        <el-input placeholder="图书名称" v-model="pagination.queryString" style="width: 200px;" class="filter-item"></el-input>

                        <el-button @click="getAll()" class="dalfBut">查询</el-button>

                        <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>

                    </div>

                    <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>

                        <el-table-column type="index" align="center" label="序号"></el-table-column>

                        <el-table-column prop="type" label="图书类别" align="center"></el-table-column>

                        <el-table-column prop="name" label="图书名称" align="center"></el-table-column>

                        <el-table-column prop="description" label="描述" align="center"></el-table-column>

                        <el-table-column label="操作" align="center">

                            <template slot-scope="scope">

                                <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>

                                <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>

                            </template>

                        </el-table-column>

                    </el-table>

                    <!-- 新增标签弹层 -->

                    <div class="add-form">

                        <el-dialog title="新增图书" :visible.sync="dialogFormVisible">

                            <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input v-model="formData.description" type="textarea"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div slot="footer" class="dialog-footer">

                                <el-button @click="dialogFormVisible = false">取消</el-button>

                                <el-button type="primary" @click="handleAdd()">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                    <!-- 编辑标签弹层 -->

                    <div class="add-form">

                        <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">

                            <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input v-model="formData.description" type="textarea"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div slot="footer" class="dialog-footer">

                                <el-button @click="dialogFormVisible4Edit = false">取消</el-button>

                                <el-button type="primary" @click="handleEdit()">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                </div>

            </div>

        </div>

    </body>

    <!-- 引入组件库 -->

    <script src="../js/vue.js"></script>

    <script src="../plugins/elementui/index.js"></script>

    <script type="text/javascript" src="../js/jquery.min.js"></script>

    <script src="../js/axios-0.18.0.js"></script>

    <script>
var vue = new Vue({ el: '#app',
data:{
pagination: {},
dataList: [],//当前页要展示的列表数据
formData: {},//表单数据
dialogFormVisible: false,//控制表单是否可见
dialogFormVisible4Edit:false,//编辑表单是否可见
rules: {//校验规则
type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
}
}, //钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
}, methods: {
//列表
getAll() {
}, //弹出添加窗口
handleCreate() {
}, //重置表单
resetForm() {
}, //添加
handleAdd () {
}, //弹出编辑窗口
handleUpdate(row) {
}, //编辑
handleEdit() {
}, // 删除
handleDelete(row) {
}
}
}) </script> </html>

SpringMVC 拦截时放行页面请求

需要在 SpringMVC配置类中配置包扫描

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}

4.2 列表功能

异步请求

//列表
getAll() {
// 发送ajax请求
axios.get("/books").then((res)=>{
// 注意第二个data是实体类中封装的属性data,二者意义不同
this.dataList = res.data.data;
});
},

4.3 添加功能

页面元素

//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
// 跳出新增窗口时对窗口中输入框的表单内容进行一次清除
this.resetForm();
}, //重置表单,清理之前添加的数据
resetForm() {
this.formData = {};
},

异步请求

//添加
handleAdd () {
// 发送ajax请求
axios.post("/books", this.formData).then((res)=>{
// alert(res.data.code);
if (res.data.code == 20011) {
// 如果操作成功,关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else if (res.data.code == 20010) {
// 此处后台没有给出响应的msg,手动补充
this.$message.error("添加失败,数据不合法");
}else {
this.$message.error(res.data.msg);
}
}).finally(()=>{
// 重新显示数据
this.getAll();
});
},

4.4 添加、修改、删除功能状态处理

存在问题

需要判断SQL语句执行成功或者失败条件

解决方案:对Dao和Service部分进行修改

Dao

@Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
// @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
public int save(Book book); @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
public int update(Book book); @Delete("delete from tbl_book where id = #{id}")
public int delete(Integer id);

Service

@Override
public boolean save(Book book) {
return bookDao.save(book) > 0;
} @Override
public boolean update(Book book) {
return bookDao.update(book) > 0;
} @Override
public boolean delete(Integer id) {
return bookDao.delete(id) > 0;
}

4.5 修改功能

页面元素

//弹出编辑窗口
handleUpdate(row) {
// console.log(row);
// 根据id查询数据
axios.get("/books/" + row.id).then((res)=>{
if (res.data.code == 20041) {
// 展示弹层,回显数据
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
} else {
this.$message.error(res.data.msg);
}
});
},

异步请求

//编辑
handleEdit() {
// 发送ajax请求
axios.put("/books", this.formData).then((res)=>{
// alert(res.data.code);
if (res.data.code == 20031) {
// 如果操作成功,关闭弹层
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
}else if (res.data.code == 20030) {
this.$message.error("修改失败,数据不合法");
}else {
this.$message.error(res.data.msg);
}
}).finally(()=>{
// 重新显示数据
this.getAll();
});
},

4.6 删除功能

// 删除
handleDelete(row) {
// 弹出提示框
this.$confirm("此操作将永久删除数据,请确认","注意",{
type:'info'
}).then(()=>{
// 确认删除
// 发送Ajax请求
axios.delete("/books/" + row.id).then((res)=>{
if (res.data.code == 20021) {
this.$message.success("删除成功");
} else {
this.$message.error("删除失败");
}
}).finally(()=>{
// 重新显示数据
this.getAll();
});
}).catch(()=>{
// 取消删除
this.$message.info("删除已取消");
});
}

5、拦截器

5.1 拦截器概念

拦截器概念:拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行

拦截器作用:(做增强)

在指定的方法调用前后执行预先设定的代码
阻止原始方法的执行

拦截器和过滤器的区别

归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强(取决于 web 服务器配置类中 SpringMVC 的访问内容设置)

拦截器图示

SpringMVC与拦截器:访问前后执行操作

工作流程:栈顺序

5.2 拦截器入门案例

5.2.1 环境准备

需要 SpringMVC 配置类和已经配置好的Controller,笔者使用上面的 SSM 整合案例的代码进行演示

5.2.2 拦截器开发

    创建拦截器类

    拦截器一般写在 controller 包下,一般只给 controller 用
    拦截器也可以写在其他位置,但是要保证 SpringMVC 配置类的包扫描可以扫描到

    package priv.dandelion.controller.interceptor;
    
    // import ...
    
    @Component
    public class ProjectInterceptor 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("afterCompletion");
    }
    }

    配置拦截器类

    此处写在了 SpringMvcSupport 中,注意配置类注解
    要保证 SpringMVC 配置类的包扫描可以扫描到

    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport { @Autowired
    private ProjectInterceptor projectInterceptor; // 配置拦截器
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
    // 使用到的两个参数均为可变参数,可以直接写多个,addResourceHandlers()中相同,不再赘述
    // registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*"); // 也可以采用这种形式,拦截/books,/books/*,/books/*/*...
    registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
    } @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    registry.addResourceHandler("/css/**").addResourceLocations("/css/");
    registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
    }

5.2.3 拦截器拦截规则

执行顺序

    执行 preHandle()【返回 true】
    执行原始方法
    执行 postHandle()
    执行afterCompletion()

boolean preHandle()方法

preHandle() 返回 true 时,按照上面的执行顺序执行
preHandle() 返回 true 时,中止原始操作的执行,原始操作后的拦截器操作也不执行

添加拦截并设定拦截的访问路径时

假设拦截内容为/book,当使用 Rest 风格时,GET /books 与 POST /book/1 不同,/book/1不会被拦截
假设拦截内容为/book*,当使用 Rest 风格时,PUT /books/1 与 POST /book/1 都会被拦截

补充:执行流程图解

5.2.4 简化SpringMvcSupport的编写

可以直接在 SpringMvcConfig 中继承 WebMvcConfigurer 接口,覆写相应方法,效果相同
相比 SpringMvcSupport 具有一定侵入性

@Configuration
@ComponentScan("priv.dandelion.controller")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer { @Autowired
private ProjectInterceptor projectInterceptor; @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
} @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}

5.3 拦截器参数

拦截器代码见 5.2.2

5.3.1 前置处理方法

request:请求对象,获取请求数据中的内容,如获取请求头的Content-Type

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contentType = request.getHeader("Content-Type");
System.out.println("preHandle..."+contentType);
return true;
}

response:响应对象,同 request

handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装。可以获取方法的相关信息

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm = (HandlerMethod)handler;
String methodName = hm.getMethod().getName();//可以获取方法的名称
System.out.println("preHandle..."+methodName);
return true;
}

5.3.2 后置处理方法

ModelAndView modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整,目前开发返回 JSON 数据较多,其使用率不高

5.3.3 完成处理方法

Exception ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理,如表现层抛出的异常。现在已经有全局异常处理器类,所以该参数的使用率也不高。

5.4 拦截器链运行顺序

规则

当配置多个拦截器时,形成拦截器链
拦截器链的运行顺序参照拦截器添加顺序为准
当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
当拦截器运行中断,仅运行配置在前面的拦截器的 afterCompletion 操作

图解

SpringMVC学习笔记 - 第二章 - SSM整合案例 - 技术整合、统一结果封装、统一异常处理、前后联调、拦截器的相关教程结束。

《SpringMVC学习笔记 - 第二章 - SSM整合案例 - 技术整合、统一结果封装、统一异常处理、前后联调、拦截器.doc》

下载本文的Word格式文档,以方便收藏与打印。