springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)

文章目录

    • 今日内容
    • 一、SSM整合【重点】
      • 1 SSM整合配置
        • 问题导入
        • 1.1 SSM整合流程
        • 1.2 SSM整合配置
          • 1.2.1 创建工程,添加依赖和插件
          • 1.2.2 Spring整合Mybatis
          • 1.2.3 Spring整合SpringMVC
      • 2 功能模块开发
        • 2.1 数据层开发(BookDao)
        • 2.2 业务层开发(BookService/BookServiceImpl)
        • 2.3 表现层开发(BookController)
      • 3 接口测试
        • 3.1 Spring整合Junit测试业务层方法 ==(开发过程中Service开发完毕要停下来用Junit进行业务层接口测试)==
        • 3.2 postman测试表现层接口 ==(开发过程中表现层开发完毕要停下来用PostMan进行表现层测试)==
    • 二、表现层数据封装【重点】
      • 问题导入
      • 1 表现层响应数据的问题
      • 2 定义Result类封装响应结果
        • 2.1 Result类封装响应结果
        • 2.2 Code类封装响应码
      • 3 表现层数据封装返回Result对象
    • 三、异常处理器【理解】
      • 问题导入
      • 1 异常介绍
      • 2 异常处理器
        • 2.2.1 编写异常处理器
        • 2.2.2 @RestControllerAdvice注解介绍
        • 2.2.3 @ExceptionHandler注解介绍
    • 四、项目异常处理方案【理解】
      • 问题导入
      • 1 项目异常分类
      • 2 项目异常处理方案
      • 3 项目异常处理代码实现
        • 3.1 根据异常分类自定义异常类
          • 3.1.1 自定义项目系统级异常
          • 3.1.2 自定义项目业务级异常
        • 3.2 自定义异常编码(持续补充)
        • 3.3 触发自定义异常
        • 3.4 在异常通知类中拦截并处理异常
    • 五、SSM整合页面开发【重点】
      • 1 准备工作
      • 2 列表查询功能
      • 3 添加功能
      • 4 修改功能
      • 5 删除功能
    • 六、拦截器【理解】
      • 1 拦截器简介
        • 问题导入
        • 1.1 拦截器概念和作用
        • 1.2 拦截器和过滤器的区别
      • 2 入门案例
        • 问题导入
        • 2.0 环境准备
        • 2.1 拦截器代码实现
          • 【第一步】定义拦截器
          • 【第二步】配置加载拦截器
        • 2.2 拦截器流程分析
      • 3 拦截器参数
        • 问题导入
        • 3.1 前置处理(▲ 实用性最强)
        • 3.2 后置处理
        • 3.3 完成后处理
      • 4 拦截器链配置
        • 问题导入
        • 4.1 多个拦截器配置
        • 4.2 多个连接器工作流程分析

今日内容

  • 能够掌握SSM整合的流程
  • 能够编写SSM整合功能模块类
  • 能够使用Result统一表现层响应结果
  • 能够编写异常处理器进行项目异常
  • 能够完成SSM整合前端页面发送请求实现增删改查操作
  • 能够编写拦截器并配置拦截器

一、SSM整合【重点】

1 SSM整合配置

问题导入

请描述“SSM整合流程”中各个配置类的作用?

1.1 SSM整合流程

  1. 创建工程
  2. SSM整合
    • Spring
      • SpringConfig
    • MyBatis
      • MybatisConfig
      • JdbcConfig
      • jdbc.properties
    • SpringMVC
      • ServletContainersInitConfig
      • SpringMvcConfig
  3. 功能模块
    • 表与实体类
    • dao(接口+自动代理)
    • service(接口+实现类)
      • 业务层接口测试(整合JUnit)
    • controller
      • 表现层接口测试(PostMan)

1.2 SSM整合配置

1.2.1 创建工程,添加依赖和插件

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第1张图片
补全一下目录结构:
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第2张图片

最终目录结构先贴出来:

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-webmvcartifactId>
        <version>5.2.10.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-jdbcartifactId>
        <version>5.2.10.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-testartifactId>
        <version>5.2.10.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.6version>
    dependency>

    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatis-springartifactId>
        <version>1.3.0version>
    dependency>

    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>

    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.16version>
    dependency>

    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>

    <dependency>
        <groupId>javax.servletgroupId>
        <artifactId>javax.servlet-apiartifactId>
        <version>3.1.0version>
        <scope>providedscope>
    dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.coregroupId>
        <artifactId>jackson-databindartifactId>
        <version>2.9.0version>
    dependency>
	
	<dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <version>1.18.26version>
    dependency>
dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.mavengroupId>
            <artifactId>tomcat7-maven-pluginartifactId>
            <version>2.1version>
            <configuration>
                <port>80port>
                <path>/path>
            configuration>
        plugin>
    plugins>
build>

下面依次创建各个文件

1.2.2 Spring整合Mybatis
  • 创建数据库和表
-- 创建ssm_db数据库
CREATE DATABASE IF NOT EXISTS ssm_db CHARACTER SET utf8;

-- 使用ssm_db数据库
USE ssm_db;

-- 创建tbl_book表
CREATE TABLE tbl_book(
    id INT PRIMARY KEY AUTO_INCREMENT, -- 图书编号
    TYPE VARCHAR(100), -- 图书类型
    NAME VARCHAR(100), -- 图书名称
    description VARCHAR(100) -- 图书描述
);
-- 添加初始化数据
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring实战 第5版','Spring入门经典教材,深入理解Spring原理技术内幕');
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring 5核心原理与30个类手写实战','十年沉淀之作,手写Spring精华思想');
INSERT INTO tbl_book VALUES(NULL,'计算机理论','Spring 5设计模式','深入Spring源码剖析,Spring源码蕴含的10大设计模式');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播就该这么做:主播高效沟通实战指南','李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播销讲实战一本通','和秋叶一起学系列网络营销书籍');
INSERT INTO tbl_book VALUES(NULL,'市场营销','直播带货:淘宝、天猫直播从新手到高手','一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

其实这些文件,可以直接创建模板文件,直接生成
对应包下右键new
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第3张图片

  • jdbc.properties属性文件 (resources根目录下)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm_db?useSSL=false
jdbc.username=root
jdbc.password=1234
  • JdbcConfig配置类
package cn.whu.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

public class JdbcConfig {

    //这里可以加载jdbc.properties里的变量
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //1.定义一个方法获得要管理的对象
    //2.添加@Bean,表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    //Spring事务管理需要的平台事务管理器对象
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
  • MybatisConfig配置类
public class MybatisConfig {

    @Bean //IoC容器中有SqlSessionFactory了
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {//@Bean注入引用类型 特别方便 提供参数即可
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        //框架的好处,大部分东西都是默认的,不用设计,那些动态变化设置一下就ok了
        ssfb.setTypeAliasesPackage("cn.whu.domain"); //
        ssfb.setDataSource(dataSource);//
        return ssfb;
    }

    //帮你造mapper 也就是Dao实现类的 (Service里的Autowired有东西自动注入了)
    @Bean //IoC容器中有 所有DaoImpl实现类了
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("cn.whu.dao");//
        return msc;
    }
}
  • SpringConfig配置类
@Configuration
@ComponentScan({"cn.whu.service"})//有些该springMVC扫描 因此这里配置得细一点
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement //开启Spring事务管理
public class SpringConfig {
}
1.2.3 Spring整合SpringMVC
  • SpringMvcConfig配置类
@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc //很多辅助功能
public class SpringMvcConfig {

}
  • ServletContainersInitConfig 配置类,加载SpringMvcConfig和SpringConfig配置类

web容器刚刚启动时 就是加载这个配置类 也即是创建2个容器 A:SpringIOC容器和B:SpringMVCIOC容器
Spring和SpringMVC分别加载各自的Bean
B可以访问A但A不可以访问B

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    // Spring核心配置文件
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
    
    /*web容器启动时 加载这两个配置类 也即是创建2个容器 A:SpringIOC容器和B:SpringMVCIOC容器 Spring和SpringMVC分别加载各自的Bean B可以访问A但A不可以访问B*/

    // SpringMVC核心配置文件(SpringMVC的Controller就是我们的Servlet嘛)
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    //配置SpringMVC拦截所有路径
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //Post请求乱码处理
    @Override //本质也是过滤器 (这里写新的方法,就相当于xml中写新的配置)
    protected Filter[] getServletFilters() {
        //可以自己写过滤器Filter,但是SpringMVC肯定有内置写好的
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("utf-8");
        return new Filter[]{filter};
    }
}

整合配置完成了,启动服务器不报错,就OK啦。备份一下

2 功能模块开发

2.1 数据层开发(BookDao)

  • Book实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
}
  • BookDao接口

接口方法 默认修饰符:public abstract

public interface BookDao {

    @Insert("insert into tbl_book(type, name, description) values(#{type},#{name},#{description})")
    void save(Book book);

    @Update("update tbl_book set TYPE=#{type},NAME=#{name},description=#{description} where id=#{id}")
    void update(Book book);

    @Delete("delete from tbl_book where id = #{id}")
    void delete(Integer id);

    @Select("select * from tbl_book where id=#{id}")
    Book getById(Integer id);

    @Select("select * from tbl_book")
    List<Book> getAll();

}

2.2 业务层开发(BookService/BookServiceImpl)

  • BookService接口

JdbcConfig配置类中配置了PlatformTransactionManager
SpringConfig核心配置类中开启了注解式事务驱动 @EnableTransactionManagement
这里就可以直接一行注解开启事务了

@Transactional //表示所有方法进行事务管理
public interface BookService {
	//接口方法 默认修饰符:public abstract 所以不用写public
	
    boolean save(Book book);

    boolean update(Book book);

    boolean delete(Integer id);

    Book getById(Integer id);

    List<Book> getAll();

}
  • BookServiceImpl实现类
@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        bookDao.save(book);
        return true;//抛出异常的时候 return false
    }

    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    public List<Book> getAll() {
        return bookDao.getAll();
    }
}

2.3 表现层开发(BookController)

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService service;

    //不返回String了 该返回啥就返回啥 异步交互的好处

    @PostMapping
    public boolean save(@RequestBody Book book){
        return service.save(book);
    }

    @PutMapping
    public boolean update(@RequestBody Book book) {
        return service.update(book);
    }

    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id) {
        return service.delete(id);
    }

    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id) {
        return service.getById(id);
    }

    @GetMapping
    public List<Book> getAll() {
        return service.getAll();
    }

}

3 接口测试

开发过程中有两个地方要停下来测试
1)Service开发完毕要停下来用Junit进行(Service)业务层接口测试
2)(Controller)表现层开发完毕要停下来用PostMan进行(Controller)表现层测试

3.1 Spring整合Junit测试业务层方法 (开发过程中Service开发完毕要停下来用Junit进行业务层接口测试)

@RunWith(SpringJUnit4ClassRunner.class)//Spring整合Junit专用的类加载器
@ContextConfiguration(classes = {SpringConfig.class})//加载spring核心配置类 把Spring用起来呀
public class BookServiceTest {

    @Autowired
    private BookService service;

    @Test
    public void testSave() {
        Book book = new Book();
        book.setType("计算机");
        book.setName("springMVC入门");
        book.setDescription("小试牛刀");
        service.save(book);
    }

    @Test
    public void testGetById(){
        Book book = service.getById(1);
        System.out.println(book);
    }

    @Test
    public void testGetAll(){
        List<Book> books = service.getAll();
        System.out.println(books);
    }
    
}

均能正常查询或者插入,后台没有问题啦~

3.2 postman测试表现层接口 (开发过程中表现层开发完毕要停下来用PostMan进行表现层测试)

先启动服务器呀

  • 测试保存图书
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第4张图片
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第5张图片

  • 测试修改图书
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第6张图片
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第7张图片

  • 测试删除图书
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第8张图片
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第9张图片

  • 测试根据Id查询图书
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第10张图片

  • 测试查询所有图书
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第11张图片

二、表现层数据封装【重点】

问题导入

目前我们表现层响应给客户端的数据有哪几种?

1 表现层响应数据的问题

问题:我们表现层增删改方法返回true或者false表示是否成功,getById()方法返回一个json对象,getAll()方法返回一个json对象数组,这里就出现了三种格式的响应结果,极其不利于前端解析。

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第12张图片

如果前后端都我们开发,这些格式其实很合理,但是现在前端不归我们写,那这些种类的返回格式得协商到什么时候啊,前后端肯定有一种统一的数据格式规范,不用怎么协商,直接就能看懂。
其实就是前后端人员通信的协议
eg:
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第13张图片

解决:我们需要统一响应结果的格式

环境:将springmvc_08_ssm原封不动复制一份为springmvc_09_ssm_result即可

2 定义Result类封装响应结果

2.1 Result类封装响应结果

controller包下新建类Result.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Result {
    private Object data;
    private Integer code;
    private String msg;

    // 加一个双参
    public Result(Object data, Integer code) {
        this.data = data;
        this.code = code;
    }
}

注意事项:

Result类中的字段并不是固定的,可以根据需要自行增减

2.2 Code类封装响应码

controller包下新建类Code.java

//状态码
public class Code {
    // 1 结尾的表示成功
    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;

    // 0 结尾的表示失败
    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;
}

注意事项:

Code类的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK

3 表现层数据封装返回Result对象

返回值类型全部改成刚刚设计的Result类型

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService service;

    //全部统一返回Result,然后再把Result对象自动转成json

    @PostMapping
    public Result save(@RequestBody Book book) {
        boolean flag = service.save(book);
        return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
    }

    @PutMapping
    public Result update(@RequestBody Book book) {
        boolean flag = service.update(book);
        return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
        boolean flag = service.delete(id);
        return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
    }

    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
        Book book = service.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> bookList = service.getAll();
        //查询0条也可能成功,可能就是没有那种数据。 但是bookList==null 一定是抛异常失败了
        Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
        String msg = bookList != null ? "" : "数据查询失败,请重试!";
        return new Result(code, bookList, msg);
    }

}

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第14张图片


springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第15张图片

三、异常处理器【理解】

问题导入

问题1:项目各个个层级均可能出现异常,异常处理代码书写在哪一层?

1 异常介绍

  • 程序开发过程中不可避免的会遇到异常现象,我们不能让用户看到这样的页面数据

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第16张图片

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

思考1:各个层级均出现异常,异常处理代码书写在哪一层?
答1:所有得异常均抛出到表现层进行处理
思考2:异常种类千千万,若在表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
答2:AOP思想呀

2 异常处理器

环境:springmvc_09_ssm_result原封不动复制一份为springmvc_09_ssm_result_exception即可

2.2.1 编写异常处理器

写在表现层Controller包下(因为是Controller要用啊,写在这里最合适了)
核心: 关键一个注解@RestControllerAdvice 容器里有他就能自动捕获Rest风格的Controller里的异常了

// springMVC扫描的就是"cn.whu.controller"包 所以此注解能被扫描到
@RestControllerAdvice //处理Rest风格开发的Controller
public class ProjectExceptionAdvice {

    @ExceptionHandler(Exception.class)//处理哪种类型的异常?  Exception.class就统一处理所有种类的异常了
    public Result doException(Exception e) {
        System.out.println("异常哪里跑? " + e);
        return new Result(666, null, "异常哪里跑?");//返回值格式还得符合协议规范
    }
}

使用异常处理器之后的效果

也返回了统一格式的数据

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第17张图片
在这里插入图片描述

2.2.2 @RestControllerAdvice注解介绍

  • 名称:@RestControllerAdvice

  • 类型:类注解

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

  • 作用:为Rest风格开发的控制器类做增强(就是AOP)

  • 说明:此注解自带@ResponseBody注解与@Component注解,具备对应的功能

2.2.3 @ExceptionHandler注解介绍

  • 名称:@ExceptionHandler
  • 类型:方法注解
  • 位置:专用于异常处理的控制器方法上方
  • 作用:设置指定异常的处理方案,功能等同于控制器方法出现异常后终止原始控制器执行,并转入当前方法执行
  • 说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常

四、项目异常处理方案【理解】

问题导入

请说出项目当前异常的分类以及对应类型异常该如何处理?

1 项目异常分类

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

2 项目异常处理方案

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

3 项目异常处理代码实现

3.1 根据异常分类自定义异常类

新建一个包:exception,在里面写两个自定义异常

3.1.1 自定义项目系统级异常
//自定义异常处理器,用于封装异常信息,对异常进行分类
@Data
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;
    }

}
3.1.2 自定义项目业务级异常

和上面一模一样 只是类名不同而已,继承的类都一样

//自定义异常处理器,用于封装异常信息,对异常进行分类
@Data
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;
    }

}

3.2 自定义异常编码(持续补充)

controller.Code类里面加一些自定义编码

public class Code {

	//之前其他状态码省略没写,以下是新补充的状态码,可以根据需要自己补充
    
    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 = 60002;
    
}

3.3 触发自定义异常

实际开发肯定不这么干,而是用AOP干

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

	//在getById演示触发异常,其他方法省略没有写进来
    // 下面代码只是为了模拟异常,所以看起来有点搞笑
    public Book getById(Integer id) {
        //比如所有异常都被我们分为2类,那么捕获到所有异常转成抛两类自定义异常
        //eg1: 模拟业务异常
        //将可能出现的异常进行包装,转换成自定义异常
        if(id < 0){
            throw new BusinessException(Code.BUSINESS_ERR,"请不要用你的技术挑战我的耐性!");
        }

        //eg2: 模拟系统异常
        //将可能出现的异常进行包装,转换成自定义异常
        try {
            int i = 1/0;
        }catch (Exception e){//怎么转换? 就是捕获到一切异常,这里都转换成throw new 自定义异常类(...)
            throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请稍后再试试!",e);
        }
        
        return bookDao.getById(id);
    }
}

3.4 在异常通知类中拦截并处理异常

controller.ProjectExceptionAdvice

// springMVC扫描的就是"cn.whu.controller"包 所以此注解能被扫描到
@RestControllerAdvice //处理Rest风格开发的Controller
public class ProjectExceptionAdvice {

    // 这里统一拦截到系统异常,并做好处理
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException e) {
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(e.getCode(),null,e.getMessage());
        //ex.getCode()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的code
    }

    // 这里统一拦截到业务异常,并做好处理
    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException e) {
        return new Result(e.getCode(),null,e.getMessage());
        //ex.getCode()和e.getMessage()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的
    }

    @ExceptionHandler(Exception.class)//处理哪种类型的异常?  Exception.class就统一处理所有种类的异常了
    public Result doException(Exception e) {
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(Code.SYSTEM_UNKNOWN_ERR,null,"系统繁忙,请稍后再试!");
        //ex.getCode()和e.getMessage()是(ServiceImpl.getById)方法内throw new SystemException(code,"info",e); 时塞进来的
    }

}

测试:在postman中发送请求访问getById方法,传递参数-1,得到以下结果:

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第18张图片
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第19张图片

五、SSM整合页面开发【重点】

将springmvc_09_ssm_result_exception复制一份为springmvc_09_ssm_result_exception_page
这次打算加上页面
链接:https://pan.baidu.com/s/1opoo-lQ-DvqOFWN2mYsLSw
提取码:z0ae
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第20张图片
复制好直接粘贴到webapp目录下
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第21张图片

1 准备工作

为了确保静态资源能够被访问到,需要设置静态资源过滤

config包下新建文件SpringMvcSupport.java

@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/");
    }
}

SpringMvcConfig的@ComponentScan要多扫描一个config包了 保证SpringMvcSupport 配置类能被扫描到
其实这两个配置类都可以直接创建成模板的

@Configuration
@ComponentScan({"cn.whu.controller","cn.whu.config"})//config是为了加载SpringMvcSupport类,以放行静态资源
@EnableWebMvc //很多辅助功能 eg: 自动类型转换 自动JSON格式转换
public class SpringMvcConfig {

}

2 列表查询功能

  • 前端代码
//列表
getAll() {
    //发送ajax请求
    axios.get("/books").then(resp => {
        //console.log(resp.data);
        this.dataList = data.data;
    })
},

就是这么简单

3 添加功能

  • 前端代码
//弹出添加窗口
handleCreate() {
    this.dialogFormVisible = true;
    this.resetForm();//否则刚点击完并弹窗时表单会自动填充上次的数据
},

//重置表单
resetForm() {
    this.formData = {}
},

//添加
handleAdd() {
    axios.post("/books",this.formData).then(resp => {
        if(resp.data.code==20011){
            this.dialogFormVisible=false;
            this.$message.success("添加成功");
        }else if(resp.data.code==20010){
            this.$message.error("添加失败");
        }else {
            this.$message.error(resp.data.msg);
        }
    }).finally(()=>{//这里也可以写finally
        this.getAll();
    })
},
  • 后台代码改进

先把bookDao接口里的增删改方法返回值改成int
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第22张图片
再让service按照影响行数返回true或者false

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
	//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
    public boolean save(Book book) {
        return bookDao.save(book) > 0;
    }
	//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
    public boolean update(Book book) {
        return bookDao.update(book) > 0;
    }
	//增删改的方法判断了影响的行数是否大于0,而不是固定返回true
    public boolean delete(Integer id) {
        return bookDao.delete(id) > 0;
    }

    public Book getById(Integer id) {
        if(id < 0){
            throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
            return bookDao.getById(id);
        }
    }
    public List<Book> getAll() {
        return bookDao.getAll();
    }
}
  • 测试: 将数据库表字段长度修改下限制
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第23张图片
    再添加type长度超过100的:
    springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第24张图片
    sql语句异常,直接走的SystemException(RuntimeException),被系统异常拦截了

4 修改功能

  • 显示弹出框查询图书信息
//弹出编辑窗口
handleUpdate(row) {
    // console.log(row);   //row.id 查询条件
    //查询数据,根据id查询
    axios.get("/books/"+row.id).then((res)=>{
        // console.log(res.data.data);
        if(res.data.code == 20041){
            //展示弹层,加载数据
            this.formData = res.data.data;
            this.dialogFormVisible4Edit = true;//这里才显示弹层
        }else{
            this.$message.error(res.data.msg);
        }
    });
}

我直接这么做的,其实也行,但是万一人家修改了前端页面,就可能出问题了,上面写法去查下数据库可以保证数据一定是数据库里原版的

//弹出编辑窗口
handleUpdate(row) {//直接就把行数据传送过来了 真好呀
    this.dialogFormVisible4Edit = true;
    this.formData = row;
},
  • 保存修改后的图书信息
//编辑
handleEdit() {
    axios.put("/books", this.formData).then(res => {
        //如果操作成功,关闭弹层,显示数据
        if (res.data.code == 20031) {
            this.$message.success("修改成功!");
            this.dialogFormVisible4Edit = false;
        } else if (res.data.code == 20030) {
            this.$message.error("修改失败!");
        } else {
            this.$message.error(res.data.msg);
        }
    }).finally(() => {
        this.getAll();
    });
},

5 删除功能

// 删除
handleDelete(row) {
    //1. 弹出提示框
    this.$confirm('此操作将永久删除该行记录, 是否继续?', '提示', {
        type: 'warning'
    }).then(() => {
        //2. 做删除业务
        axios.delete("/books/"+row.id).then(res=>{
            if(res.data.code ==20021){
                this.$message.success("删除成功!");
            }else {
                this.$message.success("删除失败!");
            }
        }).finally(() => {
            this.getAll();
        });
    }).catch(() => {
        //3. 取消删除
        this.$message.info('已取消删除');
    });
},

六、拦截器【理解】

1 拦截器简介

问题导入

问题1:拦截器拦截的对象是谁?

问题2:拦截器和过滤器有什么区别?

1.1 拦截器概念和作用

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第25张图片

  • 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
  • 作用:
    1. 在指定的方法调用前后执行预先设定的代码
    2. 阻止原始方法的执行
    3. 总结:增强
  • 核心原理:AOP思想

1.2 拦截器和过滤器的区别

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

ServletContainersInitConfig里面配置了哪些请求需要走SpringMVC
假如配置成下面这样,那么只有/books/开头的请求走SpringMVC,Interceptor只能拦截这些请求,其他请求Interceptor管不到了 (Interceptor只能管SpringMVC的,管不了spring、mybatis以及其他一些框架的)
在这里插入图片描述
当然只是现在我们是直接这么配的,就很难看清二者区别了
protected String[] getServletMappings() { return new String[]{"/"}; }

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第26张图片

2 入门案例

问题导入

定义拦截器需要实现什么接口?

2.0 环境准备

之前的springmvc_06_rest复制一份,改名为springmvc_12_interceptor,并删除不必要的文件
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第27张图片

链接:https://pan.baidu.com/s/1tbXdjN_8PxzMPEwV01AdJA
提取码:roy6

2.1 拦截器代码实现

【第一步】定义拦截器

拦截怎么做?给个接口限制一下就OK啦,你就不会瞎写啦

做法:定义一个类,实现HandlerInterceptor接口即可

controller包下新建interceptor包,再在该包下新建ProjectInterceptor类,也就是cn.whu.controller.interceptor.ProjectInterceptor
好处: @Component注解就自动被扫描到了,因为也在Controller包下

@Component //注意当前类必须受Spring容器控制
//定义拦截器类,实现HandlerInterceptor接口
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle..."+contentType);
        return true;//return false;原始controller方法就不会再被访问执行了
    }

    @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...");
    }
}
【第二步】配置加载拦截器

config.SpringMvcSupport 之前的静态资源过滤放行(addResourceHandlers)也写在这里

这么写 SpringMvcConfig 得多扫描一个包:cn.whu.Controller
(下面写法2直接写在SpringMvcConfig中简单一点)

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    //注入我们自己写的拦截器 [cn.whu.controller.interceptor.ProjectInterceptor]
    @Autowired
    private ProjectInterceptor projectInterceptor;
    // 添加拦截器
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // 请求/books (完全匹配) 时用这个拦截器拦截           【/boks/1 拦截不了哦】
        // 请求/books/* (eg:/books/1) 时也用这个拦截器拦截   【/books/s/1 拦截不到哦】
        // 请求/books/*/* (eg:/books/s/1) 时也用这个拦截器拦截
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/*");
    }
}

addPathPatterns是可变参数,随便写多少个
在这里插入图片描述

  • 写法2: 使用标准接口WebMvcConfigurer简化开发(注意:侵入式较强 和spring强行绑定到一起了)

可以删除SpringMvcSupport配置类了,他的代码直接写在SpringMvcConfig里面,这样少了一个文件SpringMvcConfig 也可以少扫描一个"cn.whu.config"包

@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    //SpringMvcSupport的内容直接写这里  上面不用再扫描 "cn.whu.config" 包了

    // 1、设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/xxx的时候,走/pages目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        // 其他静态资源依次类推 逐一 放行
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }

    //注入我们自己写的拦截器 [cn.whu.controller.interceptor.ProjectInterceptor]
    @Autowired
    private ProjectInterceptor projectInterceptor;
    // 2、添加拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        // 请求/books (完全匹配) 时用这个拦截器拦截           【/boks/1 拦截不了哦】
        // 请求/books/* (eg:/books/1) 时也用这个拦截器拦截   【/books/s/1 拦截不到哦】
        // 请求/books/*/* (eg:/books/s/1) 时也用这个拦截器拦截
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*","/books/*/*");
    }
}

2.2 拦截器流程分析

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第28张图片

3 拦截器参数

问题导入

postHandle()和afterCompletion()方法都是处理器方法执行之后执行,有什么区别?

3.1 前置处理(▲ 实用性最强)

//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("preHandle..."+contentType);
    return true;
}
  • 参数

    1. request:请求对象
    2. response:响应对象
    3. handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
      (简言之:handler是对原始执行方法的封装,有了它你就可以操作原始执行的方法)
  • 返回值
    返回值为false,被拦截的处理器将不执行。

//原始方法调用前执行的内容
//返回值类型可以拦截控制的执行,true放行,false终止
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //1、可以获取请求参数
    String contentType = request.getHeader("Content-Type");
    System.out.println(contentType);

    //2、特别好用的handler
    System.out.println(handler);//cn.whu.controller.BookController#save(Book)
    System.out.println(handler.getClass());//class org.springframework.web.method.HandlerMethod
    //2.1 不妨强转试试
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    // 哇:可以做反射里的一系列事情了
    System.out.println(handlerMethod.getBean());
    System.out.println(handlerMethod.getMethod());

    System.out.println("preHandle...");
    return true;
}

3.2 后置处理

//原始方法调用后执行的内容
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle...");
}
  • 参数
    modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行跳转(了解)

注意:如果处理器方法出现异常了,该方法不会执行

3.3 完成后处理

//原始方法调用完成后执行的内容
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("afterCompletion...");
}
  • 参数
    ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

注意:无论处理器方法内部是否出现异常,该方法都会执行。

4 拦截器链配置

其实以后很少用多拦截器,一般一个拦截器就够了。

问题导入

什么是拦截器链?

4.1 多个拦截器配置

  • 定义第二个拦截器
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...222");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...222");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...222");
    }
}
  • 配置第二个拦截器
@Configuration
@ComponentScan({"cn.whu.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor)
            .addPathPatterns("/books","/books/*");
        // 同样的路径被多个拦截器匹配,形成拦截器链 (这里添加的顺序就是拦截器链的顺序)
        registry.addInterceptor(projectInterceptor2)
            .addPathPatterns("/books","/books/*");
    }
}

再访问任意路径,eg:http://localhost/books
springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第29张图片

4.2 多个连接器工作流程分析

  • 当配置多个拦截器时,形成拦截器链
  • 拦截器链的运行顺序参照拦截器添加顺序为准(SpringMvcConfig.addInterceptors方法里的添加顺序)
  • 当拦截器中出现对原始处理器的拦截(return false),后面的(post)拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器(他们return truel了)的afterCompletion操作

有一个return false了,所有的post都不会执行。
只要拦截器return true了,对应的after一定会执行 (return false的那个拦截器的after不会执行)

springMVC02-SSM整合(Result统一响应数据格式、异常页面修改、SSM整合vue-elementUI小案例、SpringMVC的拦截器Interceptor)_第30张图片

你可能感兴趣的:(后端框架-SSM,#,springMVC,mybatis,java,spring)