文章目录

  • 今日内容
  • 一、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 创建工程,添加依赖和插件


补全一下目录结构:

最终目录结构先贴出来:

pom.xml

<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><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.26</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>

下面依次创建各个文件

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

  • 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@Beanpublic DataSource dataSource() {DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}//Spring事务管理需要的平台事务管理器对象@Beanpublic 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"); //<typeAliases>ssfb.setDataSource(dataSource);//<dataSource>return ssfb;}//帮你造mapper 也就是Dao实现类的 (Service里的Autowired有东西自动注入了)@Bean //IoC容器中有 所有DaoImpl实现类了public MapperScannerConfigurer mapperScannerConfigurer() {MapperScannerConfigurer msc = new MapperScannerConfigurer();msc.setBasePackage("cn.whu.dao");//<mappers>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 所以不用写publicboolean 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 {@Autowiredprivate 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 {@Autowiredprivate BookService service;//不返回String了 该返回啥就返回啥 异步交互的好处@PostMappingpublic boolean save(@RequestBody Book book){return service.save(book);}@PutMappingpublic 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);}@GetMappingpublic 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 {@Autowiredprivate BookService service;@Testpublic void testSave() {Book book = new Book();book.setType("计算机");book.setName("springMVC入门");book.setDescription("小试牛刀");service.save(book);}@Testpublic void testGetById(){Book book = service.getById(1);System.out.println(book);}@Testpublic void testGetAll(){List<Book> books = service.getAll();System.out.println(books);}}

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

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

先启动服务器呀

  • 测试保存图书

  • 测试修改图书

  • 测试删除图书

  • 测试根据Id查询图书

  • 测试查询所有图书

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

问题导入

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

1 表现层响应数据的问题

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

如果前后端都我们开发,这些格式其实很合理,但是现在前端不归我们写,那这些种类的返回格式得协商到什么时候啊,前后端肯定有一种统一的数据格式规范,不用怎么协商,直接就能看懂。
其实就是前后端人员通信的协议
eg:

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

环境:将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 {@Autowiredprivate BookService service;//全部统一返回Result,然后再把Result对象自动转成json@PostMappingpublic Result save(@RequestBody Book book) {boolean flag = service.save(book);return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);}@PutMappingpublic 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);}@GetMappingpublic 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);}}


三、异常处理器【理解】

问题导入

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

1 异常介绍

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

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

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

思考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, "异常哪里跑?");//返回值格式还得符合协议规范}
}

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

也返回了统一格式的数据


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 {@Autowiredprivate 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,得到以下结果:


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

将springmvc_09_ssm_result_exception复制一份为springmvc_09_ssm_result_exception_page
这次打算加上页面
链接:https://pan.baidu.com/s/1opoo-lQ-DvqOFWN2mYsLSw
提取码:z0ae

复制好直接粘贴到webapp目录下

1 准备工作

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

config包下新建文件SpringMvcSupport.java

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {@Overrideprotected 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(()=>{//这里也可以写finallythis.getAll();})
},
  • 后台代码改进

先把bookDao接口里的增删改方法返回值改成int

再让service按照影响行数返回true或者false

@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;//增删改的方法判断了影响的行数是否大于0,而不是固定返回truepublic boolean save(Book book) {return bookDao.save(book) > 0;}//增删改的方法判断了影响的行数是否大于0,而不是固定返回truepublic boolean update(Book book) {return bookDao.update(book) > 0;}//增删改的方法判断了影响的行数是否大于0,而不是固定返回truepublic 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();}
}
  • 测试: 将数据库表字段长度修改下限制

    再添加type长度超过100的:

    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 拦截器概念和作用

  • 拦截器(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[]{"/"}; }

2 入门案例

问题导入

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

2.0 环境准备

之前的springmvc_06_rest复制一份,改名为springmvc_12_interceptor,并删除不必要的文件

链接: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]@Autowiredprivate ProjectInterceptor projectInterceptor;// 添加拦截器@Overrideprotected 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]@Autowiredprivate 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 拦截器流程分析

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、特别好用的handlerSystem.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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...222");return false;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...222");}@Overridepublic 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 {@Autowiredprivate ProjectInterceptor projectInterceptor;@Autowiredprivate ProjectInterceptor2 projectInterceptor2;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置多拦截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");// 同样的路径被多个拦截器匹配,形成拦截器链 (这里添加的顺序就是拦截器链的顺序)registry.addInterceptor(projectInterceptor2).addPathPatterns("/books","/books/*");}
}

再访问任意路径,eg:http://localhost/books

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)相关推荐

  1. Day75.Ajax、拦截器Interceptor、异常映射、自动|手动类型转换、类型校验

    目录 一.Ajax ★ 1. 基本类型参数传递  @ResponseBody响应体 2. Ajax传递实体类 3.Ajax传递实体类带级联属性 (非json 普通参数) @DateTimeFormat ...

  2. Result统一响应数据

    目录 1. 响应数据的格式 1.1 状态码划分 1.3 响应数据的封装 2. 统一数据返回 1. 响应数据的格式 在分离的环境中,我们前后交互就显得尤为重要.前端按照接口文档中的URL地址和参数要求发 ...

  3. Spring Boot 统一功能处理(用户登录权限效验-拦截器、异常处理、数据格式返回)

    文章目录 1. 统一用户登录权限效验 1.1 最初用户登录权限效验 1.2 Spring AOP 统一用户登录验证 1.3 Spring 拦截器 1.4 练习:登录拦截器 1.5 拦截器实现原理 1. ...

  4. spring Boot 2 基础篇 。内含 整合一个spring boot 的 小案例

    目录 springBoot2基础篇 前言与开发环境 一.快速创建Boot项目 1.使用spring提供的快速构建 2.基于maven的手动构建 3.在Idea中隐藏指定文件/文件夹 二.SpringB ...

  5. SSM之SpringMVC 04 —— Ajax、拦截器、文件上传和下载

    系列文章 SSM之SpringMVC 01 -- SpringMVC原理及概念.Hello SpringMVC 注解版和配置版 SSM之SpringMVC 02 -- Controller和RestF ...

  6. ssm项目jsp加载不出来图片_16. SSM 搭建

    SSM:Spring.SpringMVC.Mybatis 项目完成图: 1.搭建 SpringMVC 1.1 导入spring和springMVC包 spring-webmvc servlet-api ...

  7. 【SSM框架系列】SpringMVC的文件上传、拦截器及异常处理

    SpringMVC的文件上传 服务器端实现文件上传的技术有很多种,Servlet3.0,FileUtils,框架等等,都可以实现文件上传,不管使用哪一种上传技术,都必须满足三要素: 表单项type=& ...

  8. spring—拦截器和异常

    SpringMVC的拦截器 SpringMVC拦截器-拦截器的作用 Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理. 将拦截器按一定 ...

  9. 玩转 SpringBoot 2 快速整合拦截器

    概述 首先声明一下,这里所说的拦截器是 SpringMVC 的拦截器(HandlerInterceptor).使用SpringMVC 拦截器需要做如下操作: 创建拦截器类需要实现 HandlerInt ...

最新文章

  1. 不再写死,SpringBoot实现动态增删启停定时任务
  2. Scala:Enumeration
  3. Python多线程(1)——介绍
  4. 网转 mm IOS 报表
  5. 以太坊Bloom过滤器实现原理及应用场景分析
  6. 关于类微博的timeline的设计思考
  7. Sql Server对时间(月、周)的操作
  8. Julia:从交易流水动态计算可用资金
  9. lisp用entmake生产圆柱体_液态基酒生产
  10. 线性空间的向量组与数量矩阵的乘法
  11. openflow通信流程总结
  12. Tuxera NTFS如何解决硬盘无法写入文件教程分享
  13. 期货大佬给交易者的交易箴言,值得珍藏品读!
  14. 阿里云有奖调查结果公布,赠送10个阿里巴巴logo胸针...
  15. 个人开公司的流程及费用
  16. SAP PS常用事务代码T-CODE
  17. ICO和IPO的区别
  18. 真阳率(tp)、假阳率(np)
  19. qt使用assimp加载模型_有关Assimp与Qt3D
  20. workqueue --最清晰的讲解

热门文章

  1. 蔚来汽车交付量连续2个月下滑,理想汽车再登顶,小鹏汽车剑指9月
  2. prettier配置不生效问题
  3. 英格兰功勋老将或赛季后退役:身体已吃不消
  4. [golang gin框架] 29.Gin 商城项目-用户登录,注册操作
  5. 工业电子台账最简单的例子:设置模板后一键导入数据
  6. ubuntu16.04 ssh启用root连接
  7. Tensorflow 2.x源码详解之第三章:导数(梯度/GradientTape)
  8. [Overleaf] LaTeX中的斜体、加粗、下划线和简单指令
  9. jackson中@JsonProperty、@JsonIgnore等常用注解总结
  10. 华为鸿蒙系统的手机爆光图片,华为鸿蒙系统界面曝光,图标也实在太炫酷了,彻底摆脱安卓味道!...