SSM 03_SpringMVC REST风格 Postman SSM整合 拦截器

01-SpringMVC简介

SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。

SpringMVC是处于Web层的框架,所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端,所以如何处理请求和响应是SpringMVC中非常重要的一块内容。

REST是一种软件架构风格,可以降低开发的复杂性,提高系统的可伸缩性,后期的应用也是非常广泛。

SSM整合是把咱们所学习的SpringMVC+Spring+Mybatis整合在一起来完成业务开发,是对我们所学习这三个框架的一个综合应用。

02-SpringMVC入门案例

知识点1:@Controller

名称

@Controller

类型

类注解

位置

SpringMVC控制器类定义上方

作用

设定SpringMVC的核心控制器bean

知识点2:@RequestMapping

名称

@RequestMapping

类型

类注解或方法注解

位置

SpringMVC控制器类或方法定义上方

作用

设置当前控制器方法请求访问路径

相关属性

value(默认),请求访问路径

知识点3:@ResponseBody

名称

@ResponseBody

类型

类注解或方法注解

位置

SpringMVC控制器类或方法定义上方

作用

设置当前控制器方法响应内容为当前返回值,无需解析

03-入门案例工作流程

2.4.1 启动服务器初始化过程
  1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器

  • 功能类似于以前的web.xml

  1. 执行createServletApplicationContext方法,创建了WebApplicationContext对象

  • 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器

  1. 加载SpringMvcConfig配置类

  1. 执行@ComponentScan加载对应的bean

  • 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解

  1. 加载UserController,每个@RequestMapping的名称对应一个具体的方法

  • 此时就建立了 /save 和 save方法的对应关系

  1. 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则

  • /代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求

2.4.2 单次请求过程
  1. 发送请求http://localhost/save

  1. web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理

  1. 解析请求路径/save

  1. 由/save匹配执行对应的方法save()

  • 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法

  1. 执行save()

  1. 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方

04-bean加载控制

方式一:修改Spring配置类,设定扫描范围为精准范围
@Configuration
@ComponentScan({"com.itheima.service","comitheima.dao"})
public class SpringConfig {        }

说明:

上述只是通过例子说明可以精确指定让Spring扫描对应的包结构,真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer对象来进行扫描处理的,我们只需要将其扫描到service包即可。

方式二:修改Spring配置类,设定扫描范围为itheima,排除controller包中的bean(比第一种麻烦)
@Configuration
@ComponentScan(value="com.itheima",
    excludeFilters=@ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Controller.class
    )    )
public class SpringConfig {    }
  • excludeFilters属性:设置扫描加载bean时,排除的过滤规则

  • type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除

  • ANNOTATION:按照注解排除

  • ASSIGNABLE_TYPE:按照指定的类型过滤

  • ASPECTJ:按照Aspectj表达式排除,基本上不会用

  • REGEX:按照正则表达式排除

  • CUSTOM:按照自定义规则排除

大家只需要知道第一种ANNOTATION即可

  • classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean

05-PostMan工具介绍\

对于PostMan如果觉得字小不好看,可以使用ctrl+=调大,ctrl+-调小

3.1 PostMan简介

代码编写完后,我们要想测试,只需要打开浏览器直接输入地址发送请求即可。发送的是GET请求可以直接使用浏览器,但是如果要发送的是POST请求呢?

如果要求发送的是post请求,我们就得准备页面在页面上准备form表单,测试起来比较麻烦。所以我们就需要借助一些第三方工具,如PostMan.

PostMan是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。

  • 作用:常用于进行接口测试

  • 特征: 简单 实用 美观 大方

06-设置请求映射路径

07-get请求与post请求发送普通参数

08-5种类型参数传递

常见的参数种类有:

  • 普通参数

  • POJO类型参数

  • 嵌套POJO类型参数

  • 数组类型参数

  • 集合类型参数

09-json数据传递参数

前面我们说过,流行的开发方式为异步调用。前后端以异步方式进行交换,前端如果发送JSON数据,后端如何接收?

对于JSON数据类型,我们常见的有三种:

  • json普通数组(["value1","value2","value3",...])

  • json对象({key1:value1,key2:value2,...})

  • json对象数组([{key1:value1,...},{key2:value2,...}])

10-日期型参数传递

11-!响应!

4.6.2 响应页面[了解]
步骤1:设置返回页面
@Controller
public class UserController {
    @RequestMapping("/toJumpPage")
    //注意
    //1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
    //2.方法需要返回String
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }    }
4.6.3 返回文本数据[了解]
步骤1:设置返回文本内容
@Controller
public class UserController {
       @RequestMapping("/toText")
    //注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }    }
4.6.4 响应JSON数据
响应POJO对象
@Controller
public class UserController {
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("传智播客");
        user1.setAge(15);
        User user2 = new User();
        user2.setName("黑马程序员");
        user2.setAge(12);

        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);
        return userList;
    }    }

返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖==@ResponseBody==注解和==@EnableWebMvc==注解

响应POJO集合对象
@Controller
public class UserController {
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("传智播客");
        user1.setAge(15);
        User user2 = new User();
        user2.setName("黑马程序员");
        user2.setAge(12);

        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);
        return userList;
    }    }
@ResponseBody

名称

@ResponseBody

类型

方法\类注解

位置

SpringMVC控制器方法定义上方和控制类上

作用

设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能

相关属性

pattern:指定日期时间格式字符串

说明:

  • 该注解可以写在类上或者方法上

  • 写在类上就是该类下的所有方法都有@ReponseBody功能

  • 当方法上有@ReponseBody注解后

  • 方法的返回值为字符串,会将其作为文本内容直接响应给前端

  • 方法的返回值为对象,会将对象转换成JSON响应给前端

此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:

  • 对象转Json数据(POJO -> json)

  • 集合转Json数据(Collection -> json)

12-REST风格简介

13-RESTful入门案例 (修改RESTful风格)

新增POST
@Controller
public class UserController {
    //设置当前请求方法为POST,表示REST风格中的添加操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save() {
        System.out.println("user save...");
        return "{'module':'user save'}";
    }    }
  • 将请求路径("/save")更改为/users

  • 使用method属性限定该方法的访问方式为POST

  • 如果发送的不是POST请求,比如发送GET请求,则会报错

删除DELETE
@Controller
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}
  • 将请求路径更改为/users

访问成功,但是删除方法没有携带所要删除数据的id,所以针对RESTful的开发,如何携带数据参数?

传递路径参数

前端发送请求的时候使用:http://localhost/users/1,路径中的1就是我们想要传递的参数。

后端获取参数,需要做如下修改:

  • 修改@RequestMapping的value属性,将其中修改为/users/{id},目的是和路径匹配

  • 在方法的形参前添加@PathVariable注解

思考如下两个问题:

(1)如果方法形参的名称和路径{}中的值不一致,该怎么办?

(2)如果有多个参数需要传递该如何编写?

前端发送请求的时候用:http://localhost/users/1/tom,路径中的1和tom就是我们想要传递的两个参数

后端获取参数,需要做如下修改:

@Controller
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    @RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id,@PathVariable String name) {
        System.out.println("user delete..." + id+","+name);
        return "{'module':'user delete'}";
    }
}
修改PUT
@Controller
public class UserController {
    //设置当前请求方法为PUT,表示REST风格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user) {
        System.out.println("user update..." + user);
        return "{'module':'user update'}";
    }
}
  • 将请求路径更改为/users

根据ID查询GETGET
@Controller
public class UserController {
    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }
}

将请求路径更改为/users

查询所有GETGET
@Controller
public class UserController {
    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users" ,method = RequestMethod.GET)
    @ResponseBody
    public String getAll() {
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}

将请求路径更改为/users

关于接收参数,我们学过三个注解@RequestBody@RequestParam@PathVariable

这三个注解之间的区别和应用分别是什么?

  • 区别

  • @RequestParam用于接收url地址传参或表单传参

  • @RequestBody用于接收json数据

  • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数

  • 应用

  • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广

  • 如果发送非json格式数据,选用@RequestParam接收请求参数

  • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值;超过1个JSON@RequestBody

14-RESTful快速开发(标准开发)

问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。

将@RequestMapping提到类上面,用来定义所有方法共同的访问路径

问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。

使用@GetMapping @PostMapping @PutMapping @DeleteMapping代替

问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。

1.将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能

2.使用@RestController注解替换@Controller与@ResponseBody注解,简化书写

@RestController //@Controller + ReponseBody
@RequestMapping("/books")
public class BookController {
    //@RequestMapping(method = RequestMethod.POST)
    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}"; }
    //@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";}
    //@RequestMapping(method = RequestMethod.PUT)
    @PutMapping
    public String update(@RequestBody Book book){
        System.out.println("book update..." + book);
        return "{'module':'book update'}";}
    //@RequestMapping(value = "/{id}",method = RequestMethod.GET)
    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..." + id);
        return "{'module':'book getById'}";}
    //@RequestMapping(method = RequestMethod.GET)
    @GetMapping
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";}
}

15-案例: 基于RESTful页面数据交互(后台接口开发)

5.4.2 后台接口开发
步骤1:编写Controller类并使用RESTful进行配置
@RestController
@RequestMapping("/books")
public class BookController {
    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save ==> "+ book);
        return "{'module':'book save success'}";
    }
     @GetMapping
    public List<Book> getAll(){
        System.out.println("book getAll is running ...");
        List<Book> bookList = new ArrayList<Book>();

        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVC入门教程");
        book1.setDescription("小试牛刀");
        bookList.add(book1);
        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMVC实战教程");
        book2.setDescription("一代宗师");
        bookList.add(book2);
        Book book3 = new Book();
        book3.setType("计算机丛书");
        book3.setName("SpringMVC实战教程进阶");
        book3.setDescription("一代宗师呕心创作");
        bookList.add(book3);

        return bookList;
    }    }

16-案例:基于RESTful页面数据交互(页面访问处理)

SpringMVC拦截了静态资源,根据/pages/books.html去controller找对应的方法,找不到所以会报404的错误。

(2)SpringMVC为什么会拦截静态资源呢?

(3)解决方案?

  • SpringMVC需要将静态资源进行放行。

@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {    }
或者
@Configuration
@ComponentScan("com.itheima")
@EnableWebMvc
public class SpringMvcConfig {    }

17-SSM整合 (整合配置)

(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

18-SSM整合(功能模块开发)

19-SSM整合 (接口测试)

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

目前我们就已经有三种数据类型返回给前端,如果随着业务的增长,我们需要返回的数据类型会越来越多。对于前端开发人员在解析数据的时候就比较凌乱了,所以对于前端来说,如果后台能够返回一个统一的数据结果,前端在解析的时候就可以按照一种方式进行解析。开发就会变得更加简单。

所以我们就想能不能将返回结果的数据进行统一,具体如何来做,大体的思路为:

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

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

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

根据分析,我们可以设置统一数据返回结果类

public class Result{
     //描述统一格式中的数据
    private Object data;
    //描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
    private Integer code;
    //描述统一格式中的消息,可选属性
    private String msg;    }

注意:Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。

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

2.2.2 结果封装
步骤1:创建Result类
步骤2:定义返回码Code类

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

步骤3:修改Controller类的返回值
//统一每一个控制器方法返回值
@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);
    }
}

22-SSM整合-异常处理器

前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决?

在解决问题之前,先来看下异常的种类及出现异常的原因:
  • 框架内部抛出的异常:因使用不合规导致

  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)

  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)

  • 表现层抛出的异常:因数据收集校验等规则导致(例如:不匹配的数据类型间导致异常)

  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。

思考

  1. 各个层级均出现异常,异常处理代码书写在哪一层?

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

  1. 异常的种类很多,表现层如何将所有的异常都处理到呢?

异常分类

  1. 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?

AOP

@RestControllerAdvice//标识当前类为REST风格对应的异常处理器
public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class//代表所有异常种类)//拦截异常的注释
    //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理异常
    public Result doException(Exception ex){//处理异常的方法
          System.out.println("嘿嘿,异常你哪里跑!")
    return new Result(666,null,"嘿嘿,异常你哪里跑!");
        //异常处理器类返回结果给前端
    }    }

名称

@RestControllerAdvice

类型

类注解

位置

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

作用

为Rest风格开发的控制器类做增强

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

23-SSM整合-项目异常处理

异常分类:
  • 业务异常(BusinessException)
  • 规范的用户行为产生的异常

  • 用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串

  • 不规范的用户行为操作产生的异常

  • 如用户故意传递错误数据

  • 发送对应消息传递给用户,提醒规范操作

  • 大家常见的就是提示用户名已存在或密码格式不正确等

  • 系统异常(SystemException)
  • 项目运行过程中可预计但无法避免的异常

  • 比如数据库或服务器宕机

  • 发送固定消息传递给用户,安抚用户

  • 系统繁忙,请稍后再试

  • 系统正在维护升级,请稍后再试

  • 系统出问题,请联系系统管理员等

  • 发送特定消息给运维人员,提醒维护

  • 可以发送短信、邮箱或者是公司内部通信软件

  • 记录日志

  • 发消息和记录日志对用户来说是不可见的,属于后台程序

  • 其他异常(Exception)
  • 编程人员未预期到的异常,如:用到的文件不存在

  • 发送固定消息传递给用户,安抚用户

  • 发送特定消息给编程人员,提醒维护(纳入预期范围内)

  • 一般是程序没有考虑全,比如未做非空校验等

  • 记录日志

3.3.3 异常解决方案的具体实现
步骤1:自定义异常类
//自定义异常处理器,用于封装异常信息,对异常进行分类
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;
    }
}

//自定义异常处理器,用于封装异常信息,对异常进行分类
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自定义异常即可//第4行

上面为了使code看着更专业些,我们在Code类中再新增需要的属性

步骤3:处理器类中处理自定义异常
//@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,"系统繁忙,请稍后再试!");
    }
}

对于异常我们就已经处理完成了,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。

小结

以后项目中的异常处理方式为:

24-前后台协议联调(列表功能)

添加了静态资源,SpringMVC会拦截,所有需要在SpringConfig的配置类中将静态资源进行放行。

  • 新建SpringMvcSupport

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

在SpringMvcConfig中扫描SpringMvcSupport

@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {    }

25-前后台协议联调(添加功能)

26-前后台协议联调(添加功能状态处理)

处理完新增后,会发现新增还存在一个问题,

新增成功后,再次点击新增按钮会发现之前的数据还在,这个时候就需要在新增的时候将表单内容清空。

resetForm(){
    this.formData = {};        }
handleCreate() {
    this.dialogFormVisible = true;
    this.resetForm();
}

27-前后台协议联调(修改功能)

28-前后台协议联调(删除功能)

handleDelete(row) {
    //1.弹出提示框
    this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
        type:'info'//提示框里的小符号
    }).then(()=>{
        //2.做删除业务
        axios.delete("/books/"+row.id).then((res)=>{
            if(res.data.code == 20021){
                this.$message.success("删除成功");
            }else{
                this.$message.error("删除失败");
            }
        }).finally(()=>{
            this.getAll();
        });
    }).catch(()=>{
        //3.取消删除
        this.$message.info("取消删除操作");
    });
}

29-拦截器简介

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

(2)Tomcat服务器接收到请求以后,会去判断请求的是态资源还是态资源

(3)如果是态资源,会直接Tomcat的项目部署目录下去直接访问

(4)如果是态资源,就需要交给项目的后台代码进行处理

(5)在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行

(6)然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截

(7)如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果

(8)如果不满足规则,则不进行处理

(9)这个时候,如果我们需要在每个Controller方法执行的前后添加业务,具体该如何来实现?

这个就是拦截器要做的事。

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

  • 指定的方法调用前后执行预先设定的代码

  • 阻止原始方法的执行

  • 总结:拦截器就是用来做增强

看完以后,大家会发现

  • 拦截器和过滤器在作用和执行顺序上也很相似

拦截器和过滤器之间的区别是什么?
  • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术

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

30-拦截器入门案例

5.2.1 环境准备
  • 创建一个Web的Maven项目

  • pom.xml添加SSM整合所需jar包

  • 创建对应的配置类

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
public class SpringMvcConfig{    }
5.2.2 拦截器开发
步骤1:创建拦截器类

让类实现HandlerInterceptor接口,重写接口中的三个方法。

@Component//声明扫描加载bean
public class ProjectInterceptor implements HandlerInterceptor {
    @Override//原始方法调用前执行的内容
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;
    }
    @Override//原始方法调用后执行的内容
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }
    @Override//原始方法调用完成后执行的内容
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

注意:拦截器类要被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" );
    }    }
步骤3:SpringMVC添加SpringMvcSupport包扫描
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{     }
步骤5:修改拦截器拦截规则,在addPathPatterns可变参数里面多加几个拦截路径
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
//...
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
    }
}

最后说一件事,就是拦截器中的preHandler方法,如果返回true,则代表放行,会执行原始Controller类中要请求的方法,如果返回false,则代表拦截,终止原始操作,后面的就不会再执行了。

步骤6:简化SpringMvcSupport的编写
@Configuration
@ComponentScan({"com.itheima.controller"})//这里不用扫描步骤三的.config
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但实现接口(这个类和spring的API绑定)具有一定的侵入性
//不用写SpringMVCSupport的类
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }        }

此后咱们就不用再写SpringMvcSupport类了。

最后我们来看下拦截器的执行流程:

当有拦截器后,请求会先进入preHandle方法,

如果方法返回true,则放行继续执行后面的handle[controller的方法]和后面的方法

如果返回false,则直接跳过后面方法的执行。

31-拦截器参数

5.3.1 前置处理方法

原始方法之前运行preHandle

public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {
    System.out.println("preHandle");
    return true;
}
  • request:请求对象

  • response:响应对象

  • handler:被调用的处理器(原始)对象,本质上是一个方法对象,对反射中的Method对象再包装

使用request对象可以获取请求数据中的内容,如获取请求头的Content-Type

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

使用handler参数,可以获取方法的相关信息

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

原始方法运行后运行,如果原始方法被拦截,则不执行

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

前三个参数和上面的是一致的。

modelAndView(页面跳转数据):如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整 因为咱们现在都是返回json数据,所以该参数的使用率不高。

5.3.3 完成处理方法

拦截器最后执行的方法,无论原始方法是否执行

public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) throws Exception {
    System.out.println("afterCompletion");
}

前三个参数与上面的是一致的。

ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

因为我们现在已经有全局异常处理器类,所以该参数的使用率也不高。

这三个方法中,最常用的是preHandle,在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。

32-拦截器-拦截器链配置(实际很少配置多拦截器)

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

5.4.1 配置多个拦截器
步骤1:创建拦截器类 (实现接口,并重写接口中的方法)
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...222");
        return false;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...222");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...222");
    }
}
步骤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/*");
    }
}
拦截器执行的顺序是和addInterceptors配置顺序有关。
  • 当配置多个拦截器时,形成拦截器链

  • 拦截器链的运行顺序参照拦截器添加顺序为准

  • 当拦截器中出现对原始处理器的拦截后面的拦截器均终止运行

  • 当拦截器运行中断仅运行配置在前面的拦截器的afterCompletion操作(红黄蓝线)

preHandle:与配置顺序相同,必定运行

postHandle:与配置顺序相反,可能不运行

afterCompletion:与配置顺序相反,可能不运行。

这个顺序不太好记,最终只需要把握住一个原则即可:以最终的运行结果为准

posted @ 2023-01-14 15:41  软工菜鸡  阅读(7)  评论(0编辑  收藏  举报  来源