• 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
    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframeworkgroupId>
    4. <artifactId>spring-webmvcartifactId>
    5. <version>5.2.10.RELEASEversion>
    6. dependency>
    7. <dependency>
    8. <groupId>org.springframeworkgroupId>
    9. <artifactId>spring-jdbcartifactId>
    10. <version>5.2.10.RELEASEversion>
    11. dependency>
    12. <dependency>
    13. <groupId>org.springframeworkgroupId>
    14. <artifactId>spring-testartifactId>
    15. <version>5.2.10.RELEASEversion>
    16. dependency>
    17. <dependency>
    18. <groupId>org.mybatisgroupId>
    19. <artifactId>mybatisartifactId>
    20. <version>3.5.6version>
    21. dependency>
    22. <dependency>
    23. <groupId>org.mybatisgroupId>
    24. <artifactId>mybatis-springartifactId>
    25. <version>1.3.0version>
    26. dependency>
    27. <dependency>
    28. <groupId>mysqlgroupId>
    29. <artifactId>mysql-connector-javaartifactId>
    30. <version>5.1.47version>
    31. dependency>
    32. <dependency>
    33. <groupId>com.alibabagroupId>
    34. <artifactId>druidartifactId>
    35. <version>1.1.16version>
    36. dependency>
    37. <dependency>
    38. <groupId>junitgroupId>
    39. <artifactId>junitartifactId>
    40. <version>4.12version>
    41. <scope>testscope>
    42. dependency>
    43. <dependency>
    44. <groupId>javax.servletgroupId>
    45. <artifactId>javax.servlet-apiartifactId>
    46. <version>3.1.0version>
    47. <scope>providedscope>
    48. dependency>
    49. <dependency>
    50. <groupId>com.fasterxml.jackson.coregroupId>
    51. <artifactId>jackson-databindartifactId>
    52. <version>2.9.0version>
    53. dependency>
    54. dependencies>
    55. <build>
    56. <plugins>
    57. <plugin>
    58. <groupId>org.apache.tomcat.mavengroupId>
    59. <artifactId>tomcat7-maven-pluginartifactId>
    60. <version>2.1version>
    61. <configuration>
    62. <port>80port>
    63. <path>/path>
    64. configuration>
    65. plugin>
    66. plugins>
    67. build>
    步骤 3: 创建项目包结构

    config 目录存放的是相关的配置类
    controller 编写的是 Controller
    dao 存放的是 Dao 接口,因为使用的是 Mapper 接口代理方式,所以没有实现类包
    service 存的是 Service 接口, impl 存放的是 Service 实现类
    resources: 存入的是配置文件,如 Jdbc.properties
    webapp: 目录可以存放静态资源
    test/java: 存放的是测试类
    步骤 4: 创建 SpringConfig 配置类
    1. @Configuration
    2. @ComponentScan({"com.itheima.service"})
    3. @PropertySource("classpath:jdbc.properties")
    4. @Import({JdbcConfig.class,MyBatisConfig.class})
    5. @EnableTransactionManagement
    6. public class SpringConfig {
    7. }
    步骤 5: 创建 JdbcConfig 配置类
    1. public class JdbcConfig {
    2. @Value("${jdbc.driver}")
    3. private String driver;
    4. @Value("${jdbc.url}")
    5. private String url;
    6. @Value("${jdbc.username}")
    7. private String username;
    8. @Value("${jdbc.password}")
    9. private String password;
    10. @Bean
    11. public DataSource dataSource(){
    12. DruidDataSource dataSource = new DruidDataSource();
    13. dataSource.setDriverClassName(driver);
    14. dataSource.setUrl(url);
    15. dataSource.setUsername(username);
    16. dataSource.setPassword(password);
    17. return dataSource;
    18. }
    19. @Bean
    20. public PlatformTransactionManager transactionManager(DataSource dataSource){
    21. DataSourceTransactionManager ds = new DataSourceTransactionManager();
    22. ds.setDataSource(dataSource);
    23. return ds;
    24. }
    25. }
    步骤 6: 创建 MybatisConfig 配置类
    1. public class MyBatisConfig {
    2. @Bean
    3. public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
    4. SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    5. factoryBean.setDataSource(dataSource);
    6. factoryBean.setTypeAliasesPackage("com.itheima.domain");
    7. return factoryBean;
    8. }
    9. @Bean
    10. public MapperScannerConfigurer mapperScannerConfigurer(){
    11. MapperScannerConfigurer msc = new MapperScannerConfigurer();
    12. msc.setBasePackage("com.itheima.dao");
    13. return msc;
    14. }
    15. }
    步骤 7: 创建 jdbc.properties
    resources 下提供 jdbc.properties, 设置数据库连接四要素
    1. jdbc.driver=com.mysql.jdbc.Driver
    2. jdbc.url=jdbc:mysql://localhost:3306/ssm_db
    3. jdbc.username=root
    4. jdbc.password=root
    步骤 8: 创建 SpringMVC 配置类
    1. @Configuration
    2. @ComponentScan("com.itheima.controller")
    3. @EnableWebMvc
    4. public class SpringMvcConfig {
    5. }
    步骤 9: 创建 Web 项目入口配置类
    1. public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    2. protected Class[] getRootConfigClasses() {
    3. return new Class[]{SpringConfig.class};
    4. }
    5. protected Class[] getServletConfigClasses() {
    6. return new Class[]{SpringMvcConfig.class};
    7. }
    8. protected String[] getServletMappings() {
    9. return new String[]{"/"};
    10. }
    11. @Override
    12. protected Filter[] getServletFilters() {
    13. CharacterEncodingFilter filter = new CharacterEncodingFilter();
    14. filter.setEncoding("UTF-8");
    15. return super.getServletFilters();
    16. }
    17. }
    至此 SSM 整合的环境就已经搭建好了。在这个环境上,我们如何进行功能模块的开发呢 ?

    1.3功能模块开发

    需求 : 对表 tbl_book 进行新增、修改、删除、根据 ID 查询和查询所有
    1. -- ----------------------------
    2. -- Table structure for tbl_book
    3. -- ----------------------------
    4. DROP TABLE IF EXISTS `tbl_book`;
    5. CREATE TABLE `tbl_book` (
    6. `id` int(11) NOT NULL AUTO_INCREMENT,
    7. `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    8. `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    9. `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    10. PRIMARY KEY (`id`) USING BTREE
    11. ) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    12. -- ----------------------------
    13. -- Records of tbl_book
    14. -- ----------------------------
    15. INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
    16. INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
    17. INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
    18. INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
    19. INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
    20. INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
    21. INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
    22. INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
    23. INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
    24. INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
    25. INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
    26. INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
    步骤 2: 编写模型类
    1. package com.itheima.domain;
    2. public class Book {
    3. private Integer id;
    4. private String type;
    5. private String name;
    6. private String description;
    7. @Override
    8. public String toString() {
    9. return "Book{" +
    10. "id=" + id +
    11. ", type='" + type + '\'' +
    12. ", name='" + name + '\'' +
    13. ", description='" + description + '\'' +
    14. '}';
    15. }
    16. public Integer getId() {
    17. return id;
    18. }
    19. public void setId(Integer id) {
    20. this.id = id;
    21. }
    22. public String getType() {
    23. return type;
    24. }
    25. public void setType(String type) {
    26. this.type = type;
    27. }
    28. public String getName() {
    29. return name;
    30. }
    31. public void setName(String name) {
    32. this.name = name;
    33. }
    34. public String getDescription() {
    35. return description;
    36. }
    37. public void setDescription(String description) {
    38. this.description = description;
    39. }
    40. }
    步骤 3: 编写 Dao 接口
    1. package com.itheima.dao;
    2. import com.itheima.domain.Book;
    3. import org.apache.ibatis.annotations.Delete;
    4. import org.apache.ibatis.annotations.Insert;
    5. import org.apache.ibatis.annotations.Select;
    6. import org.apache.ibatis.annotations.Update;
    7. import java.util.List;
    8. public interface BookDao {
    9. // @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
    10. @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    11. public void save(Book book);
    12. @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    13. public void update(Book book);
    14. @Delete("delete from tbl_book where id = #{id}")
    15. public void delete(Integer id);
    16. @Select("select * from tbl_book where id = #{id}")
    17. public Book getById(Integer id);
    18. @Select("select * from tbl_book")
    19. public List getAll();
    20. }
    步骤 4: 编写 Service 接口和实现类
    1. package com.itheima.service;
    2. import com.itheima.domain.Book;
    3. import org.springframework.transaction.annotation.Transactional;
    4. import java.util.List;
    5. @Transactional
    6. public interface BookService {
    7. /**
    8. * 保存
    9. * @param book
    10. * @return
    11. */
    12. public boolean save(Book book);
    13. /**
    14. * 修改
    15. * @param book
    16. * @return
    17. */
    18. public boolean update(Book book);
    19. /**
    20. * 按id删除
    21. * @param id
    22. * @return
    23. */
    24. public boolean delete(Integer id);
    25. /**
    26. * 按id查询
    27. * @param id
    28. * @return
    29. */
    30. public Book getById(Integer id);
    31. /**
    32. * 查询全部
    33. * @return
    34. */
    35. public List getAll();
    36. }
    1. package com.itheima.service.impl;
    2. import com.itheima.dao.BookDao;
    3. import com.itheima.domain.Book;
    4. import com.itheima.service.BookService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Service;
    7. import java.util.List;
    8. @Service
    9. public class BookServiceImpl implements BookService {
    10. @Autowired
    11. private BookDao bookDao;
    12. public boolean save(Book book) {
    13. bookDao.save(book);
    14. return true;
    15. }
    16. public boolean update(Book book) {
    17. bookDao.update(book);
    18. return true;
    19. }
    20. public boolean delete(Integer id) {
    21. bookDao.delete(id);
    22. return true;
    23. }
    24. public Book getById(Integer id) {
    25. return bookDao.getById(id);
    26. }
    27. public List getAll() {
    28. return bookDao.getAll();
    29. }
    30. }
    说明 :
    bookDaoService中注入的会提示一个红线提示,为什么呢?
    BookDao 是一个接口,没有实现类,接口是不能创建对象的,所以最终注入的应该是代理对象
    代理对象是由 Spring IOC 容器来创建管理的
    IOC 容器又是在 Web 服务器启动的时候才会创建
    IDEA 在检测依赖关系的时候,没有找到适合的类注入,所以会提示错误提示
    但是程序运行的时候,代理对象就会被创建,框架会使用 DI 进行注入,所以程序运行无影响。
    如何解决上述问题?
    可以不用理会,因为运行是正常的
    设置错误提示级别

     步骤5:编写Contorller

    1. package com.itheima.controller;
    2. import com.itheima.domain.Book;
    3. import com.itheima.service.BookService;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.web.bind.annotation.*;
    6. import java.util.List;
    7. @RestController
    8. @RequestMapping("/books")
    9. public class BookController {
    10. @Autowired
    11. private BookService bookService;
    12. @PostMapping
    13. public boolean save(@RequestBody Book book) {
    14. return bookService.save(book);
    15. }
    16. @PutMapping
    17. public boolean update(@RequestBody Book book) {
    18. return bookService.update(book);
    19. }
    20. @DeleteMapping("/{id}")
    21. public boolean delete(@PathVariable Integer id) {
    22. return bookService.delete(id);
    23. }
    24. @GetMapping("/{id}")
    25. public Book getById(@PathVariable Integer id) {
    26. return bookService.getById(id);
    27. }
    28. @GetMapping
    29. public List getAll() {
    30. return bookService.getAll();
    31. }
    32. }

    1.4 单元测试

    步骤 1: 新建测试类
    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @ContextConfiguration(classes = SpringConfig.class)
    3. public class BookServiceTest {
    4. }
    步骤 2: 注入 Service
     
    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @ContextConfiguration(classes = SpringConfig.class)
    3. public class BookServiceTest {
    4. @Autowired
    5. private BookService bookService;
    6. }
    步骤 3: 编写测试方法
    我们先来对查询进行单元测试。
    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @ContextConfiguration(classes = SpringConfig.class)
    3. public class BookServiceTest {
    4. @Autowired
    5. private BookService bookService;
    6. @Test
    7. public void testGetById(){
    8. Book book = bookService.getById(1);
    9. System.out.println(book);
    10. }
    11. @Test
    12. public void testGetAll(){
    13. List all = bookService.getAll();
    14. System.out.println(all);
    15. }
    16. }

    1.5PostMan测试

    新增

     修改

     删除

     查询单个

     查询所有

    2.统一结果封装

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

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

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

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

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

    2.2.1 环境准备

     借用上面的SSM整合

    2.2.2 结果封装

    对于结果封装,我们应该是在表现层进行处理,所以我们把结果类放在 controller 包下,当然你也
    可以放在 domain 包,这个都是可以的,具体如何实现结果封装,具体的步骤为 :
    步骤 1: 创建 Result
    1. public class Result {
    2. //描述统一格式中的数据
    3. private Object data;
    4. //描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
    5. private Integer code;
    6. //描述统一格式中的消息,可选属性
    7. private String msg;
    8. public Result() {
    9. }
    10. public Result(Integer code,Object data) {
    11. this.data = data;
    12. this.code = code;
    13. }
    14. public Result(Integer code, Object data, String msg) {
    15. this.data = data;
    16. this.code = code;
    17. this.msg = msg;
    18. }
    19. public Object getData() {
    20. return data;
    21. }
    22. public void setData(Object data) {
    23. this.data = data;
    24. }
    25. public Integer getCode() {
    26. return code;
    27. }
    28. public void setCode(Integer code) {
    29. this.code = code;
    30. }
    31. public String getMsg() {
    32. return msg;
    33. }
    34. public void setMsg(String msg) {
    35. this.msg = msg;
    36. }
    37. }
    步骤 2: 定义返回码 Code
    1. package com.itheima.controller;
    2. //状态码
    3. public class Code {
    4. public static final Integer SAVE_OK = 20011;
    5. public static final Integer DELETE_OK = 20021;
    6. public static final Integer UPDATE_OK = 20031;
    7. public static final Integer GET_OK = 20041;
    8. public static final Integer SAVE_ERR = 20010;
    9. public static final Integer DELETE_ERR = 20020;
    10. public static final Integer UPDATE_ERR = 20030;
    11. public static final Integer GET_ERR = 20040;
    12. }
    注意 : code 类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为
    GET_OK,GET_ALL_OK,GET_PAGE_OK 等。
    步骤 3: 修改 Controller 类的返回值
    1. package com.itheima.controller;
    2. import com.itheima.domain.Book;
    3. import com.itheima.service.BookService;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.web.bind.annotation.*;
    6. import java.util.List;
    7. //统一每一个控制器方法返回值
    8. @RestController
    9. @RequestMapping("/books")
    10. public class BookController {
    11. @Autowired
    12. private BookService bookService;
    13. @PostMapping
    14. public Result save(@RequestBody Book book) {
    15. boolean flag = bookService.save(book);
    16. return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);
    17. }
    18. @PutMapping
    19. public Result update(@RequestBody Book book) {
    20. boolean flag = bookService.update(book);
    21. return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);
    22. }
    23. @DeleteMapping("/{id}")
    24. public Result delete(@PathVariable Integer id) {
    25. boolean flag = bookService.delete(id);
    26. return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);
    27. }
    28. @GetMapping("/{id}")
    29. public Result getById(@PathVariable Integer id) {
    30. Book book = bookService.getById(id);
    31. Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
    32. String msg = book != null ? "" : "数据查询失败,请重试!";
    33. return new Result(code,book,msg);
    34. }
    35. @GetMapping
    36. public Result getAll() {
    37. List bookList = bookService.getAll();
    38. Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;
    39. String msg = bookList != null ? "" : "数据查询失败,请重试!";
    40. return new Result(code,bookList,msg);
    41. }
    42. }
    步骤4:启动服务测试

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

    3.统一异常处理

    3.1 问题描述

    在讲解这一部分知识点之前,我们先来演示个效果,修改 BookController 类的 getById 方法
    1. @GetMapping("/{id}")
    2. public Result getById(@PathVariable Integer id) {
    3. //手动添加一个错误信息
    4. if(id == 1){
    5. int i = 1/0;
    6. }
    7. Book book = bookService.getById(id);
    8. Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
    9. String msg = book != null ? "" : "数据查询失败,请重试!";
    10. return new Result(code,book,msg);
    11. }
    重新启动运行项目,使用 PostMan 发送请求,当传入的 id 1 ,则会出现如下效果:

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

    3.2 异常处理器的使用

    3.2.1 环境准备

    同样使用SSM整合

    3.2.2 使用步骤

    步骤 1: 创建异常处理器类
    1. //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
    2. @RestControllerAdvice
    3. public class ProjectExceptionAdvice {
    4. //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    5. @ExceptionHandler(Exception.class)
    6. public Result doOtherException(Exception ex){
    7. System.out.println("嘿嘿,异常你哪里跑")
    8. }
    9. }
    确保 SpringMvcConfig 能够扫描到异常处理器类
    步骤 2: 让程序抛出异常
    修改 BookController getById 方法,添加 int i = 1/0 .
    1. @GetMapping("/{id}")
    2. public Result getById(@PathVariable Integer id) {
    3. //手动添加一个错误信息
    4. if(id == 1){
    5. int i = 1/0;
    6. }
    7. Book book = bookService.getById(id);
    8. Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
    9. String msg = book != null ? "" : "数据查询失败,请重试!";
    10. return new Result(code,book,msg);
    11. }
    步骤3:运行程序,测试

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

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

     

     

    3.3.1 异常分类

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

    系统异常(SystemException)

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

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

     

    3.3.2 异常解决方案

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

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

    思路 :
    1. 先通过自定义异常,完成 BusinessException SystemException 的定义
    2. 将其他异常包装成自定义异常类型
    3. 在异常处理器类中对不同的异常进行处理
    步骤 1: 自定义异常类
    1. package com.itheima.exception;
    2. //自定义异常处理器,用于封装异常信息,对异常进行分类
    3. public class SystemException extends RuntimeException{
    4. private Integer code;
    5. public Integer getCode() {
    6. return code;
    7. }
    8. public void setCode(Integer code) {
    9. this.code = code;
    10. }
    11. public SystemException(Integer code, String message) {
    12. super(message);
    13. this.code = code;
    14. }
    15. public SystemException(Integer code, String message, Throwable cause) {
    16. super(message, cause);
    17. this.code = code;
    18. }
    19. }
    1. package com.itheima.exception;
    2. //自定义异常处理器,用于封装异常信息,对异常进行分类
    3. public class BusinessException extends RuntimeException{
    4. private Integer code;
    5. public Integer getCode() {
    6. return code;
    7. }
    8. public void setCode(Integer code) {
    9. this.code = code;
    10. }
    11. public BusinessException(Integer code, String message) {
    12. super(message);
    13. this.code = code;
    14. }
    15. public BusinessException(Integer code, String message, Throwable cause) {
    16. super(message, cause);
    17. this.code = code;
    18. }
    19. }
    说明 :
    让自定义异常类继承 RuntimeException 的好处是,后期在抛出这两个异常的时候,就不用在
    try...catch... throws
    自定义异常类中添加 code 属性的原因是为了更好的区分异常是来自哪个业务的
    步骤 2: 将其他异常包成自定义异常
    假如在 BookServiceImpl getById 方法抛异常了,该如何来包装呢 ?
    1. public Book getById(Integer id) {
    2. //模拟业务异常,包装成自定义异常
    3. if(id == 1){
    4. throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的 耐性!"); }
    5. //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
    6. try{
    7. int i = 1/0;
    8. }catch (Exception e){
    9. throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重 试!",e);
    10. }
    11. return bookDao.getById(id);
    12. }
    具体的包装方式有:
    方式一 : try{}catch(){} catch 中重新 throw 我们自定义异常即可。
    方式二 : 直接 throw 自定义异常即可
    上面为了使 code 看着更专业些,我们在 Code 类中再新增需要的属性
    1. package com.itheima.controller;
    2. public class Code {
    3. public static final Integer SAVE_OK = 20011;
    4. public static final Integer DELETE_OK = 20021;
    5. public static final Integer UPDATE_OK = 20031;
    6. public static final Integer GET_OK = 20041;
    7. public static final Integer SAVE_ERR = 20010;
    8. public static final Integer DELETE_ERR = 20020;
    9. public static final Integer UPDATE_ERR = 20030;
    10. public static final Integer GET_ERR = 20040;
    11. public static final Integer SYSTEM_ERR = 50001;
    12. public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    13. public static final Integer SYSTEM_UNKNOW_ERR = 59999;
    14. public static final Integer BUSINESS_ERR = 60002;
    15. }
    步骤 3: 处理器类中处理自定义异常
    1. package com.itheima.controller;
    2. import com.itheima.exception.BusinessException;
    3. import com.itheima.exception.SystemException;
    4. import org.springframework.web.bind.annotation.ExceptionHandler;
    5. import org.springframework.web.bind.annotation.RestControllerAdvice;
    6. //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
    7. @RestControllerAdvice
    8. public class ProjectExceptionAdvice {
    9. //@ExceptionHandler用于设置当前处理器类对应的异常类型
    10. @ExceptionHandler(SystemException.class)
    11. public Result doSystemException(SystemException ex){
    12. //记录日志
    13. //发送消息给运维
    14. //发送邮件给开发人员,ex对象发送给开发人员
    15. return new Result(ex.getCode(),null,ex.getMessage());
    16. }
    17. @ExceptionHandler(BusinessException.class)
    18. public Result doBusinessException(BusinessException ex){
    19. return new Result(ex.getCode(),null,ex.getMessage());
    20. }
    21. //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    22. @ExceptionHandler(Exception.class)
    23. public Result doOtherException(Exception ex){
    24. //记录日志
    25. //发送消息给运维
    26. //发送邮件给开发人员,ex对象发送给开发人员
    27. return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
    28. }
    29. }
    步骤 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
    1. <dependencies>
    2. <dependency>
    3. <groupId>javax.servletgroupId>
    4. <artifactId>javax.servlet-apiartifactId>
    5. <version>3.1.0version>
    6. <scope>providedscope>
    7. dependency>
    8. <dependency>
    9. <groupId>org.springframeworkgroupId>
    10. <artifactId>spring-webmvcartifactId>
    11. <version>5.2.10.RELEASEversion>
    12. dependency>
    13. <dependency>
    14. <groupId>com.fasterxml.jackson.coregroupId>
    15. <artifactId>jackson-databindartifactId>
    16. <version>2.9.0version>
    17. dependency>
    18. dependencies>
    19. <build>
    20. <plugins>
    21. <plugin>
    22. <groupId>org.apache.tomcat.mavengroupId>
    23. <artifactId>tomcat7-maven-pluginartifactId>
    24. <version>2.1version>
    25. <configuration>
    26. <port>80port>
    27. <path>/path>
    28. configuration>
    29. plugin>
    30. <plugin>
    31. <groupId>org.apache.maven.pluginsgroupId>
    32. <artifactId>maven-compiler-pluginartifactId>
    33. <configuration>
    34. <source>8source>
    35. <target>8target>
    36. configuration>
    37. plugin>
    38. plugins>
    39. build>
    创建对应的配置类
    1. public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    2. protected Class[] getRootConfigClasses() {
    3. return new Class[0];
    4. }
    5. protected Class[] getServletConfigClasses() {
    6. return new Class[]{SpringMvcConfig.class};
    7. }
    8. protected String[] getServletMappings() {
    9. return new String[]{"/"};
    10. }
    11. //乱码处理
    12. @Override
    13. protected Filter[] getServletFilters() {
    14. CharacterEncodingFilter filter = new CharacterEncodingFilter();
    15. filter.setEncoding("UTF-8");
    16. return new Filter[]{filter};
    17. }
    18. }
    1. @Configuration
    2. @ComponentScan({"com.itheima.controller"})
    3. @EnableWebMvc
    4. //实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
    5. public class SpringMvcConfig {
    6. }
    创建模型类 Book
    1. public class Book {
    2. private String name;
    3. private double price;
    4. public String getName() {
    5. return name;
    6. }
    7. public void setName(String name) {
    8. this.name = name;
    9. }
    10. public double getPrice() {
    11. return price;
    12. }
    13. public void setPrice(double price) {
    14. this.price = price;
    15. }
    16. @Override
    17. public String toString() {
    18. return "Book{" +
    19. "书名='" + name + '\'' +
    20. ", 价格=" + price +
    21. '}';
    22. }
    23. }
    编写 Controller
    1. @RestController
    2. @RequestMapping("/books")
    3. public class BookController {
    4. @PostMapping
    5. public String save(@RequestBody Book book){
    6. System.out.println("book save..." + book);
    7. return "{'module':'book save'}";
    8. }
    9. @DeleteMapping("/{id}")
    10. public String delete(@PathVariable Integer id){
    11. System.out.println("book delete..." + id);
    12. return "{'module':'book delete'}";
    13. }
    14. @PutMapping
    15. public String update(@RequestBody Book book){
    16. System.out.println("book update..."+book);
    17. return "{'module':'book update'}";
    18. }
    19. @GetMapping("/{id}")
    20. public String getById(@PathVariable Integer id){
    21. System.out.println("book getById..."+id);
    22. return "{'module':'book getById'}";
    23. }
    24. @GetMapping
    25. public String getAll(){
    26. System.out.println("book getAll...");
    27. return "{'module':'book getAll'}";
    28. }
    29. }
    最终创建好的项目结构如下:

     

    5.2.2 拦截器开发

    步骤 1: 创建拦截器类
    让类实现 HandlerInterceptor 接口,重写接口中的三个方法。
    1. @Component
    2. //定义拦截器类,实现HandlerInterceptor接口
    3. //注意当前类必须受Spring容器控制
    4. public class ProjectInterceptor implements HandlerInterceptor {
    5. @Override
    6. //原始方法调用前执行的内容
    7. //返回值类型可以拦截控制的执行,true放行,false终止
    8. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    9. String contentType = request.getHeader("Content-Type");
    10. HandlerMethod hm = (HandlerMethod)handler;
    11. System.out.println("preHandle..."+contentType);
    12. return true;
    13. }
    14. @Override
    15. //原始方法调用后执行的内容
    16. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    17. System.out.println("postHandle...");
    18. }
    19. @Override
    20. //原始方法调用完成后执行的内容
    21. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    22. System.out.println("afterCompletion...");
    23. }
    24. }
    注意 : 拦截器类要被 SpringMVC 容器扫描到。
    步骤 2: 配置拦截器类
    1. @Configuration
    2. public class SpringMvcSupport extends WebMvcConfigurationSupport {
    3. @Autowired
    4. private ProjectInterceptor projectInterceptor;
    5. @Override
    6. protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    7. registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    8. }
    9. @Override
    10. protected void addInterceptors(InterceptorRegistry registry) {
    11. //配置拦截器
    12. registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    13. }
    14. }
    步骤 3:SpringMVC 添加 SpringMvcSupport 包扫描
    1. @Configuration
    2. @ComponentScan({"com.itheima.controller","com.itheima.config"})
    3. @EnableWebMvc
    4. public class SpringMvcConfig implements WebMvcConfigurer {
    5. }
    步骤 4: 运行程序测试
    使用 PostMan 发送 http://localhost/books

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

    addPathPatterns 方法配置的拦截路径是 /books , 我们现在发送的是 /books/100 ,所以没有匹配
    上,因此没有拦截,拦截器就不会执行。
    步骤 5: 修改拦截器拦截规则
    1. @Configuration
    2. public class SpringMvcSupport extends WebMvcConfigurationSupport {
    3. @Autowired
    4. private ProjectInterceptor projectInterceptor;
    5. @Override
    6. protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    7. registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    8. }
    9. @Override
    10. protected void addInterceptors(InterceptorRegistry registry) {
    11. //配置拦截器
    12. registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    13. }
    14. }
    这个时候,如果再次访问 http://localhost/books/100 ,拦截器就会被执行。
    最后说一件事,就是拦截器中的 preHandler 方法,如果返回 true, 则代表放行,会执行原始
    Controller 类中要请求的方法,如果返回 false ,则代表拦截,后面的就不会再执行了。
    步骤 6: 简化 SpringMvcSupport 的编写
    1. @Configuration
    2. @ComponentScan({"com.itheima.controller"})
    3. @EnableWebMvc
    4. //实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
    5. public class SpringMvcConfig implements WebMvcConfigurer {
    6. @Autowired
    7. private ProjectInterceptor projectInterceptor;
    8. @Autowired
    9. private ProjectInterceptor2 projectInterceptor2;
    10. @Override
    11. public void addInterceptors(InterceptorRegistry registry) {
    12. //配置多拦截器
    13. registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    14. registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
    15. }
    16. }
    最后我们来看下拦截器的执行流程:

     

    5.3 拦截器参数

    5.3.1 前置处理方法

    原始方法之前运行 preHandle

     

    5.3.2 后置处理方法

     5.3.3 完成处理方法

    5.4 拦截器链配置

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

    5.4.1 配置多个拦截器

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

    1. @Component
    2. public class ProjectInterceptor2 implements HandlerInterceptor {
    3. @Override
    4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    5. // String contentType = request.getHeader("Content-Type");
    6. // HandlerMethod hm = (HandlerMethod)handler;
    7. System.out.println("preHandle...222");
    8. return false;
    9. }
    10. @Override
    11. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    12. System.out.println("postHandle...222");
    13. }
    14. @Override
    15. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    16. System.out.println("afterCompletion...222");
    17. }
    18. }
    步骤 2: 配置拦截器类
    1. @Configuration
    2. @ComponentScan({"com.itheima.controller"})
    3. @EnableWebMvc
    4. //实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
    5. public class SpringMvcConfig implements WebMvcConfigurer {
    6. @Autowired
    7. private ProjectInterceptor projectInterceptor;
    8. @Autowired
    9. private ProjectInterceptor2 projectInterceptor2;
    10. @Override
    11. public void addInterceptors(InterceptorRegistry registry) {
    12. //配置多拦截器
    13. registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    14. registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");
    15. }
    16. }
    拦截器执行的顺序是和配置顺序有关。就和前面所提到的运维人员进入机房的案例,先进后出
    当配置多个拦截器时,形成拦截器链
    拦截器链的运行顺序参照拦截器添加顺序为准
    当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
    当拦截器运行中断,仅运行配置在前面的拦截器的 afterCompletion 操作

     

  • 相关阅读:
    MySQL安装及应用合集(6):MySQL函数快速验证方法
    CIE A-Level化学Paper 1真题讲解(5)
    leetcode-10-[150]逆波兰表达式求值 [239]滑动窗口最大值[347]前k个高频元素
    乘方(pow) CSP-J2022
    Linux基本指令
    SpringBoot 官方文档示例:(54)统一异常处理之@ControllerAdvice注解
    windows逆向的工具 (没有Android工具)
    Vector | Graph:蚂蚁首个开源Graph RAG框架设计解读
    OTN电层的保护&SNCP保护详解
    RK3568平台 sys虚拟文件系统添加节点
  • 原文地址:https://blog.csdn.net/weixin_65440201/article/details/126824476