SpringMVC---SSM整合&&异常处理&&拦截器

目录

1.SSM整合

1.1 流程分析

1.2整合配置

1.3功能模块开发

1.4 单元测试

2.统一结果封装

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

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

2.2.1 环境准备

2.2.2 结果封装

3.统一异常处理

3.1 问题描述

3.2 异常处理器的使用

3.2.1 环境准备

3.2.2 使用步骤

3.3.1 异常分类

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

3.3.4小结

5,拦截器

5.1 拦截器概念

5.2 拦截器入门案例

5.2.1 环境准备

5.2.2 拦截器开发

5.3 拦截器参数

5.3.1 前置处理方法

5.3.2 后置处理方法

5.4 拦截器链配置

5.4.1 配置多个拦截器


1.SSM整合

整合Mybatis,Spring,SpringMVC三个框架。

1.1 流程分析

(1) 创建工程
        创建一个Maven web 工程
        pom.xml添加 SSM 需要的依赖 jar
        编写Web 项目的入口配置类,实现 AbstractAnnotationConfigDispatcherServletInitializer
        重写以下方法
                getRootConfigClasses() :返回Spring 的配置类 -> 需要 SpringConfig 配置类
                getServletConfigClasses() :返回SpringMVC 的配置类 -> 需要 SpringMvcConfig
                置类
                getServletMappings() : 设置SpringMVC 请求拦截路径规则
                getServletFilters() :设置过滤器,解决POST 请求中文乱码问题
(2)SSM整合[重点是各个配置的编写]
        SpringConfig
                标识该类为配置类 @Configuration
                扫描Service所在的包 @ComponentScan
                在Service层要管理事务 @EnableTransactionManagement
                读取外部的properties配置文件 @PropertySource
                整合Mybatis需要引入Mybatis相关配置类 @Import
                        第三方数据源配置类 JdbcConfig
                                构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素,
                                @Bean @Value
                                构建平台事务管理器,DataSourceTransactionManager,@Bean
                        Mybatis配置类 MybatisConfig
                                构建SqlSessionFactoryBean并设置别名扫描与数据源,@Bean
                                构建MapperScannerConfigurer并设置DAO层的包扫描
        SpringMvcConfig
                标识该类为配置类 @Configuration
                扫描Controller所在的包 @ComponentScan
                开启SpringMVC注解支持 @EnableWebMvc
(3)功能模块[与具体的业务模块有关]
        创建数据库表
        
        根据数据库表创建对应的模型类
        通过Dao层完成数据库表的增删改查(接口+自动代理)
        编写Service[Service接口+实现类]
                @Service
                @Transactional
        整合Junit对业务层进行单元测试
                @RunWith
                @ContextConfiguration
                @Test
        编写Controller
                接收请求 @RequestMapping @GetMapping @PostMapping @PutMapping
                @DeleteMapping
                接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
                        @RequestParam
                        @PathVariable
                        @RequestBody
        
                转发业务层
                        @Autowired
                响应结果
                        @ResponseBody

1.2整合配置

步骤 1 :创建 Maven web 项目
可以使用 Maven 的骨架创建

步骤 2: 添加依赖
pom.xml 添加 SSM 所需要的依赖 jar
  <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>
步骤 3: 创建项目包结构

config 目录存放的是相关的配置类
controller 编写的是 Controller
dao 存放的是 Dao 接口,因为使用的是 Mapper 接口代理方式,所以没有实现类包
service 存的是 Service 接口, impl 存放的是 Service 实现类
resources: 存入的是配置文件,如 Jdbc.properties
webapp: 目录可以存放静态资源
test/java: 存放的是测试类
步骤 4: 创建 SpringConfig 配置类
@Configuration
@ComponentScan({"com.itheima.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
步骤 5: 创建 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;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ds = new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
        return ds;
    }
}
步骤 6: 创建 MybatisConfig 配置类
public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.itheima.domain");
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }

}
步骤 7: 创建 jdbc.properties
resources 下提供 jdbc.properties, 设置数据库连接四要素
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=root
步骤 8: 创建 SpringMVC 配置类
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
步骤 9: 创建 Web 项目入口配置类
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    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 super.getServletFilters();
    }
}
至此 SSM 整合的环境就已经搭建好了。在这个环境上,我们如何进行功能模块的开发呢 ?

1.3功能模块开发

需求 : 对表 tbl_book 进行新增、修改、删除、根据 ID 查询和查询所有
-- ----------------------------
-- Table structure for tbl_book
-- ----------------------------
DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

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

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
步骤 3: 编写 Dao 接口
package com.itheima.dao;

import com.itheima.domain.Book;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

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();
}
步骤 4: 编写 Service 接口和实现类
package com.itheima.service;

import com.itheima.domain.Book;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
public interface BookService {

    /**
     * 保存
     * @param book
     * @return
     */
    public boolean save(Book book);

    /**
     * 修改
     * @param book
     * @return
     */
    public boolean update(Book book);

    /**
     * 按id删除
     * @param id
     * @return
     */
    public boolean delete(Integer id);

    /**
     * 按id查询
     * @param id
     * @return
     */
    public Book getById(Integer id);

    /**
     * 查询全部
     * @return
     */
    public List<Book> getAll();
}
package com.itheima.service.impl;

import com.itheima.dao.BookDao;
import com.itheima.domain.Book;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

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

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

    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();
    }
}
说明 :
bookDaoService中注入的会提示一个红线提示,为什么呢?
BookDao 是一个接口,没有实现类,接口是不能创建对象的,所以最终注入的应该是代理对象
代理对象是由 Spring IOC 容器来创建管理的
IOC 容器又是在 Web 服务器启动的时候才会创建
IDEA 在检测依赖关系的时候,没有找到适合的类注入,所以会提示错误提示
但是程序运行的时候,代理对象就会被创建,框架会使用 DI 进行注入,所以程序运行无影响。
如何解决上述问题?
可以不用理会,因为运行是正常的
设置错误提示级别

 步骤5:编写Contorller

package com.itheima.controller;

import com.itheima.domain.Book;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private BookService bookService;

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

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

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

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

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

1.4 单元测试

步骤 1: 新建测试类

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



}
步骤 2: 注入 Service
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

}
步骤 3: 编写测试方法
我们先来对查询进行单元测试。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

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

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

}

1.5PostMan测试

新增

 修改

 删除

 查询单个

 查询所有

2.统一结果封装

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

为了方便前端对数据进行处理,我们需要在表现层对数据结果进行封装,具体封装形式如下:

为了封装返回的结果数据 : 创建结果模型类,封装数据到 data 属性中
为了封装返回的数据是何种操作及是否操作成功 : 封装操作结果到 code 属性中
操作失败后为了封装返回的错误信息 : 封装特殊消息到 message(msg) 属性中

根据分析,我们可以设置统一数据返回结果类
public class Result{
 private Object data;
 private Integer code;
 private String msg;
}
注意 : Result 类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。

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

2.2.1 环境准备

 借用上面的SSM整合

2.2.2 结果封装

对于结果封装,我们应该是在表现层进行处理,所以我们把结果类放在 controller 包下,当然你也
可以放在 domain 包,这个都是可以的,具体如何实现结果封装,具体的步骤为 :
步骤 1: 创建 Result
public class Result {
    //描述统一格式中的数据
    private Object data;
    //描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
    private Integer code;
    //描述统一格式中的消息,可选属性
    private String msg;

    public Result() {
    }

    public Result(Integer code,Object data) {
        this.data = data;
        this.code = code;
    }

    public Result(Integer code, Object data, String msg) {
        this.data = data;
        this.code = code;
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
步骤 2: 定义返回码 Code
package com.itheima.controller;

//状态码
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;
}
注意 : code 类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为
GET_OK,GET_ALL_OK,GET_PAGE_OK 等。
步骤 3: 修改 Controller 类的返回值
package com.itheima.controller;

import com.itheima.domain.Book;
import com.itheima.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//统一每一个控制器方法返回值
@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> bookList = bookService.getAll();
        Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
        String msg = bookList != null ? "" : "数据查询失败,请重试!";
        return new Result(code,bookList,msg);
    }
}
步骤4:启动服务测试

至此,我们的返回结果就已经能以一种统一的格式返回给前端。前端根据返回的结果,先从中获取
code , 根据 code 判断,如果成功则取 data 属性的值,如果失败,则取 msg 中的值做提示。

3.统一异常处理

3.1 问题描述

在讲解这一部分知识点之前,我们先来演示个效果,修改 BookController 类的 getById 方法
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {

        //手动添加一个错误信息
        if(id == 1){
            int i = 1/0;
        }
        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);
    }
重新启动运行项目,使用 PostMan 发送请求,当传入的 id 1 ,则会出现如下效果:

前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决 ?
在解决问题之前,我们先来看下异常的种类及出现异常的原因 :
框架内部抛出的异常:因使用不合规导致
数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些
异常是不能避免的。所以我们就得将异常进行处理。
思考
1. 各个层级均出现异常,异常处理代码书写在哪一层 ?
所有的异常均抛出到表现层进行处理
2. 异常的种类很多,表现层如何将所有的异常都处理到呢 ?
异常分类
3. 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决 ?
AOP
对于上面这些问题及解决方案, SpringMVC 已经为我们提供了一套解决方案 :
异常处理器 :
集中的、统一的处理项目中出现的异常。

3.2 异常处理器的使用

3.2.1 环境准备

同样使用SSM整合

3.2.2 使用步骤

步骤 1: 创建异常处理器类
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
    //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    @ExceptionHandler(Exception.class)
    public Result doOtherException(Exception ex){
        System.out.println("嘿嘿,异常你哪里跑")
    }
}
确保 SpringMvcConfig 能够扫描到异常处理器类
步骤 2: 让程序抛出异常
修改 BookController getById 方法,添加 int i = 1/0 .
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {

        //手动添加一个错误信息
        if(id == 1){
            int i = 1/0;
        }
        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);
    }

​
步骤3:运行程序,测试

 说明异常已经被拦截并执行了doException方法。

异常处理器类返回结果给前端
启动运行程序,测试

 

 

3.3.1 异常分类

异常处理器我们已经能够使用了,那么在咱们的项目中该如何来处理异常呢 ?
因为异常的种类有很多,如果每一个异常都对应一个 @ExceptionHandler ,那得写多少个方法来处
理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类 :
业务异常(BusinessException)
规范的用户行为产生的异常
用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
不规范的用户行为操作产生的异常
如用户故意传递错误数据

系统异常(SystemException)

项目运行过程中可预计但无法避免的异常

比如数据库或服务器宕机
其他异常(Exception)
编程人员未预期到的异常,如 : 用到的文件不存在

 

3.3.2 异常解决方案

业务异常(BusinessException)
发送对应消息传递给用户,提醒规范操作
大家常见的就是提示用户名已存在或密码格式不正确等
系统异常(SystemException)
发送固定消息传递给用户,安抚用户
系统繁忙,请稍后再试
系统正在维护升级,请稍后再试
系统出问题,请联系系统管理员等
发送特定消息给运维人员,提醒维护
可以发送短信、邮箱或者是公司内部通信软件
记录日志
发消息和记录日志对用户来说是不可见的,属于后台程序
其他异常(Exception)
发送固定消息传递给用户,安抚用户
发送特定消息给编程人员,提醒维护(纳入预期范围内)
一般是程序没有考虑全,比如未做非空校验等
记录日志

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

思路 :
1. 先通过自定义异常,完成 BusinessException SystemException 的定义
2. 将其他异常包装成自定义异常类型
3. 在异常处理器类中对不同的异常进行处理
步骤 1: 自定义异常类
package com.itheima.exception;
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = 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;
    }

}
package com.itheima.exception;
//自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = 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;
    }

}
说明 :
让自定义异常类继承 RuntimeException 的好处是,后期在抛出这两个异常的时候,就不用在
try...catch... throws
自定义异常类中添加 code 属性的原因是为了更好的区分异常是来自哪个业务的
步骤 2: 将其他异常包成自定义异常
假如在 BookServiceImpl getById 方法抛异常了,该如何来包装呢 ?
public Book getById(Integer id) {
    //模拟业务异常,包装成自定义异常 
    if(id == 1){
        throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的 耐性!"); }
    //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常 
    try{
        int i = 1/0; 
    }catch (Exception e){
         throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重 试!",e);     
    }
    return bookDao.getById(id); 
}
具体的包装方式有:
方式一 : try{}catch(){} catch 中重新 throw 我们自定义异常即可。
方式二 : 直接 throw 自定义异常即可
上面为了使 code 看着更专业些,我们在 Code 类中再新增需要的属性
package com.itheima.controller;

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;

    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKNOW_ERR = 59999;

    public static final Integer BUSINESS_ERR = 60002;



}
步骤 3: 处理器类中处理自定义异常
package com.itheima.controller;

import com.itheima.exception.BusinessException;
import com.itheima.exception.SystemException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
    //@ExceptionHandler用于设置当前处理器类对应的异常类型
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex){
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    @ExceptionHandler(Exception.class)
    public Result doOtherException(Exception ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
    }
}
步骤 4: 运行程序
根据 ID查询,如果传入的参数为 1 ,会报 BusinessException

如果传入的是其他参数,会报SystemException

3.3.4小结

首先创建两个自定义异常处理器,继承RuntimeException,并且其方法(主要作用是对异常进行封装),当程序运行出错时,将异常throw/try...catch给响应的自定义处理器,然后在处理类ProjectException中对异常进行拦截操作。

5,拦截器

5.1 拦截器概念

(1)浏览器发送一个请求会先到Tomcatweb服务器

(2)Tomcat 服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
(3) 如果是静态资源,会直接到 Tomcat 的项目部署目录下去直接访问
(4) 如果是动态资源,就需要交给项目的后台代码进行处理
(5) 在找到具体的方法之前,我们可以去配置过滤器 ( 可以配置多个 ) ,按照顺序进行执行
(6) 然后进入到到中央处理器 (SpringMVC 中的内容 ) SpringMVC 会根据配置的规则进行拦截
(7) 如果满足规则,则进行处理,找到其对应的 controller 类中的方法进行执行 , 完成后返回结果
(8) 如果不满足规则,则不进行处理
(9) 这个时候,如果我们需要在每个 Controller 方法执行的前后添加业务,具体该如何来实现 ?
这个就是拦截器要做的事。
拦截器( Interceptor )是一种动态拦截方法调用的机制,在 SpringMVC 中动态拦截控制器方法的执行
作用:
在指定的方法调用前后执行预先设定的代码
阻止原始方法的执行
总结:拦截器就是用来做增强
拦截器和过滤器之间的区别是什么?
归属不同: Filter 属于 Servlet 技术, Interceptor 属于 SpringMVC 技术
拦截内容不同: Filter 对所有访问进行增强, Interceptor 仅针对 SpringMVC 的访问进行增强

5.2 拦截器入门案例

5.2.1 环境准备

创建一个 Web Maven 项目
pom.xml 添加 SSM 整合所需 jar
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
  </dependencies>

  <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>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
    </plugins>
  </build>
创建对应的配置类
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

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

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

    //乱码处理
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig {

}
创建模型类 Book
public class Book {
    private String name;
    private double price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "书名='" + name + '\'' +
                ", 价格=" + price +
                '}';
    }
}
编写 Controller
@RestController
@RequestMapping("/books")
public class BookController {

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

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

    @PutMapping
    public String update(@RequestBody Book book){
        System.out.println("book update..."+book);
        return "{'module':'book update'}";
    }

    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..."+id);
        return "{'module':'book getById'}";
    }

    @GetMapping
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}
最终创建好的项目结构如下:

 

5.2.2 拦截器开发

步骤 1: 创建拦截器类
让类实现 HandlerInterceptor 接口,重写接口中的三个方法。
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm = (HandlerMethod)handler;
        System.out.println("preHandle..."+contentType);
        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...");
    }
}
注意 : 拦截器类要被 SpringMVC 容器扫描到。
步骤 2: 配置拦截器类
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

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

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}
步骤 3:SpringMVC 添加 SpringMvcSupport 包扫描
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
}
步骤 4: 运行程序测试
使用 PostMan 发送 http://localhost/books

如果发送http://localhost/books/100会发现拦截器没有被执行,原因是拦截器的

addPathPatterns 方法配置的拦截路径是 /books , 我们现在发送的是 /books/100 ,所以没有匹配
上,因此没有拦截,拦截器就不会执行。
步骤 5: 修改拦截器拦截规则
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

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

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}
这个时候,如果再次访问 http://localhost/books/100 ,拦截器就会被执行。
最后说一件事,就是拦截器中的 preHandler 方法,如果返回 true, 则代表放行,会执行原始
Controller 类中要请求的方法,如果返回 false ,则代表拦截,后面的就不会再执行了。
步骤 6: 简化 SpringMvcSupport 的编写
@Configuration
@ComponentScan({"com.itheima.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/*");
    }
}
最后我们来看下拦截器的执行流程:

 

5.3 拦截器参数

5.3.1 前置处理方法

原始方法之前运行 preHandle

 

5.3.2 后置处理方法

 5.3.3 完成处理方法

5.4 拦截器链配置

目前,我们在项目中只添加了一个拦截器,如果有多个,该如何配置 ? 配置多个后,执行顺序是什么 ?

5.4.1 配置多个拦截器

步骤 1: 创建拦截器类
实现接口,并重写接口中的方法

@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        String contentType = request.getHeader("Content-Type");
//        HandlerMethod hm = (HandlerMethod)handler;
        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");
    }
}
步骤 2: 配置拦截器类
@Configuration
@ComponentScan({"com.itheima.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/*");
    }
}
拦截器执行的顺序是和配置顺序有关。就和前面所提到的运维人员进入机房的案例,先进后出
当配置多个拦截器时,形成拦截器链
拦截器链的运行顺序参照拦截器添加顺序为准
当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
当拦截器运行中断,仅运行配置在前面的拦截器的 afterCompletion 操作

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值