SpringMVC学习--黑马
SpringMVC概述
学习SpringMVC我们先来回顾下现在web程序是如何做的,咱们现在web程序大都基于三层架构来实现。
三层架构
-
浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据
-
如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利
-
将后端服务器Servlet拆分成三层,分别是
web
、service
和dao
- web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
- service层主要负责业务逻辑的处理
- dao层主要负责数据的增删改查操作
-
servlet处理请求和数据的时候,存在的问题是一个servlet只能处理一个请求
-
针对web层进行了优化,采用了MVC设计模式,将其设计为
controller
、view
和Model
- controller负责请求和数据的接收,接收后将其转发给service进行业务处理
- service根据需要会调用dao对数据进行增删改查
- dao把数据处理完后将结果交给service,service再交给controller
- controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
- 这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作。
随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。
- 因为是异步调用,所以后端不需要返回view视图,将其去除
- 前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成json格式进行返回
- SpringMVC主要负责的就是
- controller如何接收请求和数据
- 如何将请求和数据转发给业务层
- 如何将响应数据转换成json发回到前端
介绍了这么多,对SpringMVC进行一个定义
-
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
-
优点
- 使用简单、开发便捷(相比于Servlet)
- 灵活性强
注意事项
-
SpringMVC是基于Spring的,在pom.xml只导入了
spring-webmvc
jar包的原因是它会自动依赖spring相关坐标 -
AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类
-
AbstractDispatcherServletInitializer提供了三个接口方法供用户实现
- createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
- getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
- createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
- createServletApplicationContext用来加载SpringMVC环境
- createRootApplicationContext用来加载Spring环境
知识点1:@Controller
名称 | @Controller |
---|---|
类型 | 类注解 |
位置 | SpringMVC控制器类定义上方 |
作用 | 设定SpringMVC的核心控制器bean |
知识点2:@RequestMapping
名称 | @RequestMapping |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
知识点3:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | 类注解或方法注解 |
位置 | SpringMVC控制器类或方法定义上方 |
作用 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
@ResponseBody 作用:
将controller的方法返回的对象通过设当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者xml数据
注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,它的效果等同于通过response对象输出指定格式的数据
工作流程解析
为了更好的使用SpringMVC,我们将SpringMVC的使用过程总共分两个阶段来分析,分别是启动服务器初始化过程
和单次请求过程
启动服务器初始化过程
-
服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 功能类似于以前的web.xml
-
执行createServletApplicationContext方法,创建了WebApplicationContext对象
- 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
-
加载SpringMvcConfig配置类
-
执行@ComponentScan加载对应的bean
- 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
-
加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 此时就建立了
/save
和 save方法的对应关系
- 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
/
代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
单次请求过程
- 发送请求
http://localhost/save
- web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方
bean加载控制
在SpringMVC的配置类SpringMvcConfig
中使用注解@ComponentScan
,我们只需要将其扫描范围设置到controller即可,如
在Spring的配置类SpringConfig
中使用注解@ComponentScan
,当时扫描的范围中其实是已经包含了controller,如:
如何避免Spring错误加载到SpringMVC的bean?
- 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
- 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean
- 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]
五种参数类型传递
常见的参数种类有:
- 普通参数
- POJO类型参数
- 嵌套POJO类型参数
- 数组类型参数
- 集合类型参数
这些参数如何发送,后台改如何接收?我们一个个来学习。
普通参数
- 普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
如果形参与地址参数名不一致该如何解决?
发送请求与参数:
http://localhost/commonParamDifferentName?name=张三&age=18
后台接收参数:
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
因为前端给的是name
,后台接收使用的是userName
,两个名称对不上,导致接收数据失败
解决方案:使用@RequestParam注解
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestPaam("name") String userName , int age){
System.out.println("普通参数传递 userName ==> "+userName);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param different name'}";
}
注意:写上@RequestParam注解框架就不需要自己去解析注入,能提升框架处理性能
POJO数据类型
简单数据类型一般处理的是参数个数比较少的请求,如果参数比较多,那么后台接收参数的时候就比较复杂,这个时候我们可以考虑使用POJO数据类型。
- POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数
此时需要使用前面准备好的POJO类,先来看下User
public class User {
private String name;
private int age;
//setter...getter...略
}
发送请求和参数:
后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
注意:
- POJO参数接收,前端GET和POST发送请求数据的方式不变。
- 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
嵌套POJO类型参数
如果POJO对象中嵌套了其他的POJO类,如
public class Address {
private String province;
private String city;
//setter...getter...略
}
public class User {
private String name;
private int age;
private Address address;
//setter...getter...略
}
- 嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数
发送请求和参数:
后台接收参数:
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}
注意:
请求参数key的名称要和POJO中属性的名称一致,否则无法封装
数组类型参数
举个简单的例子,如果前端需要获取用户的爱好,爱好绝大多数情况下都是多个,如何发送请求数据和接收数据呢?
- 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
发送请求和参数:
后台接收参数:
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
集合类型参数
数组能接收多个值,那么集合是否也可以实现这个功能呢?
发送请求和参数:
后台接收参数:
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
运行会报错,错误的原因是:SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。
解决方案是:使用@RequestParam
注解
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
- 对于简单数据类型使用数组会比集合更简单些。
知识点1:@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
JSON数据传输参数
SpringMVC接收JSON数据的实现步骤为:
(1)导入jackson包
(2)使用PostMan发送JSON数据
(3)开启SpringMVC注解驱动,在配置类上添加@EnableWebMvc注解
(4)Controller方法的参数前添加@RequestBody注解
知识点1:@EnableWebMvc
名称 | @EnableWebMvc |
---|---|
类型 | 配置类注解 |
位置 | SpringMVC配置类定义上方 |
作用 | 开启SpringMVC多项辅助功能(如开启 json数据类型自动转换) |
知识点2:@RequestBody
名称 | @RequestBody |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
@RequestBody与@RequestParam区别
-
区别
- @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
- @RequestBody用于接收json数据【application/json】
-
应用
- 后期开发中,发送json格式数据为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
日期类型参数传递
知识点1:@DateTimeFormat
名称 | @DateTimeFormat |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参前面 |
作用 | 设定日期时间型数据格式 |
相关属性 | pattern:指定日期时间格式字符串 |
响应
知识点1:@ResponseBody
名称 | @ResponseBody |
---|---|
类型 | 方法\类注解 |
位置 | SpringMVC控制器方法定义上方和控制类上 |
作用 | 设置当前控制器返回值作为响应体, 写在类上,该类的所有方法都有该注解功能 |
相关属性 | pattern:指定日期时间格式字符串 |
说明:
- 该注解可以写在类上或者方法上
- 写在类上就是该类下的所有方法都有@ReponseBody功能
- 当方法上有@ReponseBody注解后
- 方法的返回值为字符串,会将其作为文本内容直接响应给前端
- 方法的返回值为对象,会将对象转换成JSON响应给前端
此处又使用到了类型转换,内部还是通过Converter接口的实现类完成的,所以Converter除了前面所说的功能外,它还可以实现:
- 对象转Json数据(POJO -> json)
- 集合转Json数据(Collection -> json)
Rest风格
REST简介
-
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格
当我们想表示一个网络资源的时候,可以使用两种方式:
- 传统风格资源描述形式
http://localhost/user/getById?id=1
查询id为1的用户信息http://localhost/user/saveUser
保存用户信息
- REST风格描述形式
http://localhost/user/1
http://localhost/user
- 传统风格资源描述形式
REST的优点有:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
- 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
http://localhost/users
查询全部用户信息 GET(查询)http://localhost/users/1
查询指定用户信息 GET(查询)http://localhost/users
添加用户信息 POST(新增/保存)http://localhost/users
修改用户信息 PUT(修改/更新)http://localhost/users/1
删除用户信息 DELETE(删除)
但是注意:
- 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
Restful入门
知识点1:@PathVariable
名称 | @PathVariable |
---|---|
类型 | 形参注解 |
位置 | SpringMVC控制器方法形参定义前面 |
作用 | 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应 |
Restful快速开发
关于接收参数,我们学过三个注解@RequestBody
、@RequestParam
、@PathVariable
,这三个注解之间的区别和应用分别是什么?
- 区别 * @RequestParam用于接收url地址传参或表单传参 * @RequestBody用于接收json数据 * @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
- 应用 * 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广 * 如果发送非json格式数据,选用@RequestParam接收请求参数 * 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值
问题1:每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。
将@RequestMapping提到类上面,用来定义所有方法共同的访问路径。
问题2:每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。
使用@GetMapping @PostMapping @PutMapping @DeleteMapping代替
问题3:每个方法响应json都需要加上@ResponseBody注解,重复性太高。
1.将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能
2.使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
知识点1:@RestController
名称 | @RestController |
---|---|
类型 | 类注解 |
位置 | 基于SpringMVC的RESTful开发控制器类定义上方 |
作用 | 设置当前控制器类为RESTful风格, 等同于@Controller与@ResponseBody两个注解组合功能 |
知识点2:@GetMapping @PostMapping @PutMapping @DeleteMapping
名称 | @GetMapping @PostMapping @PutMapping @DeleteMapping |
---|---|
类型 | 方法注解 |
位置 | 基于SpringMVC的RESTful开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作, 例如@GetMapping对应GET请求 |
相关属性 | value(默认):请求访问路径 |
整合SSM
基本配置:
http://t.csdn.cn/RBZvH
统一结果封装
表现层与前端数据传输协议定义
SSM整合以及功能模块开发完成后,接下来,我们在上述案例的基础上分析下有哪些问题需要我们去解决下。首先第一个问题是:
-
在Controller层增删改返回给前端的是boolean类型数据
-
在Controller层查询单个返回给前端的是对象
-
在Controller层查询所有返回给前端的是集合对象
目前我们就已经有三种数据类型返回给前端,如果随着业务的增长,我们需要返回的数据类型会越来越多。对于前端开发人员在解析数据的时候就比较凌乱了,所以对于前端来说,如果后台能够返回一个统一的数据结果,前端在解析的时候就可以按照一种方式进行解析。开发就会变得更加简单。
所以我们就想能不能将返回结果的数据进行统一,具体如何来做,大体的思路为:
- 为了封装返回的结果数据:创建结果模型类,封装数据到data属性中
- 为了封装返回的数据是何种操作及是否操作成功:封装操作结果到code属性中
- 操作失败后为了封装返回的错误信息:封装特殊消息到message(msg)属性中
根据分析,我们可以设置统一数据返回结果类
public class Result{
private Object data;
private Integer code;
private String msg;
}
**注意:**Result类名及类中的字段并不是固定的,可以根据需要自行增减提供若干个构造方法,方便操作。
案例举例
Result结果类
Code类
统一异常处理
问题描述
在讲解这一部分知识点之前,我们先来演示个效果,修改BookController类的getById
方法
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
//手动添加一个错误信息
if(id==1){
int i = 1/0;
}
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}
重新启动运行项目,使用PostMan发送请求,当传入的id为1,则会出现如下效果:
前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决?
在解决问题之前,我们先来看下异常的种类及出现异常的原因:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。
思考
-
各个层级均出现异常,异常处理代码书写在哪一层?
所有的异常均抛出到表现层进行处理
-
异常的种类很多,表现层如何将所有的异常都处理到呢?
异常分类
-
表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?
AOP
对于上面这些问题及解决方案,SpringMVC已经为我们提供了一套解决方案:
-
异常处理器:
-
集中的、统一的处理项目中出现的异常。
-
3.2 异常处理器的使用
3.2.1 环境准备
- 创建一个Web的Maven项目
- pom.xml添加SSM整合所需jar包
- 创建对应的配置类
- 编写Controller、Service接口、Service实现类、Dao接口和模型类
- resources下提供jdbc.properties配置文件
内容参考前面的项目或者直接使用前面的项目进行本节内容的学习。
3.2.2 使用步骤
步骤1:创建异常处理器类
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public void doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
}
}
确保SpringMvcConfig能够扫描到异常处理器类
步骤2:让程序抛出异常
修改BookController
的getById方法,添加int i = 1/0
.
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
int i = 1/0;
Book book = bookService.getById(id);
Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code,book,msg);
}
步骤3:运行程序,测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reCitJL9-1661418153146)(assets/1630658350945.png)]
说明异常已经被拦截并执行了doException
方法。
异常处理器类返回结果给前端
//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
System.out.println("嘿嘿,异常你哪里跑!")
return new Result(666,null,"嘿嘿,异常你哪里跑!");
}
}
启动运行程序,测试
至此,就算后台执行的过程中抛出异常,最终也能按照我们和前端约定好的格式返回给前端。
知识点1:@RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | Rest风格开发的控制器增强类定义上方 |
作用 | 为Rest风格开发的控制器类做增强 |
**说明:**此注解自带@ResponseBody注解与@Component注解,具备对应的功能
知识点2:@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
**说明:**此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
拦截器
拦截器概念
(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的访问进行增强
拦截器开发
步骤1:创建拦截器类
让类实现HandlerInterceptor接口,重写接口中的三个方法。
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
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{
}
步骤4:运行程序测试
使用PostMan发送http://localhost/books
如果发送http://localhost/books/100
会发现拦截器没有被执行,原因是拦截器的addPathPatterns
方法配置的拦截路径是/books
,我们现在发送的是/books/100
,所以没有匹配上,因此没有拦截,拦截器就不会执行。
步骤5:修改拦截器拦截规则
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
}
}
这个时候,如果再次访问http://localhost/books/100
,拦截器就会被执行。
最后说一件事,就是拦截器中的preHandler
方法,如果返回true,则代表放行,会执行原始Controller类中要请求的方法,如果返回false,则代表拦截,后面的就不会再执行了。
步骤6:简化SpringMvcSupport的编写
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
@Configuration
@ComponentScan({“com.itheima.controller”})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns(“/books”,“/books/*”);
}
}
此后咱们就不用再写SpringMvcSupport类了。
拦截器参数
前置处理方法
原始方法之前运行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;
}
后置处理方法
原始方法运行后运行,如果原始方法被拦截,则不执行
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
前三个参数和上面的是一致的。
modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
因为咱们现在都是返回json数据,所以该参数的使用率不高。
完成处理方法
拦截器最后执行的方法,无论原始方法是否执行
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion");
}
前三个参数与上面的是一致的。
ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
因为我们现在已经有全局异常处理器类,所以该参数的使用率也不高。
这三个方法中,最常用的是preHandle,在这个方法中可以通过返回值来决定是否要进行放行,我们可以把业务逻辑放在该方法中,如果满足业务则返回true放行,不满足则返回false拦截。
拦截器链配置
配置多个拦截器
拦截器执行的顺序是和配置顺序有关,先进后出。
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作
preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行。
这个顺序不太好记,最终只需要把握住一个原则即可:以最终的运行结果为准