五、SpringMVC其他扩展
1、全局异常处理机制
1、异常处理的两种方式
1、编程式异常
1、是指在代码中显式地编写处理异常的逻辑。
2、它通常涉及到对异常类型的检测及其处理
3、例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。
4、在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
5、就相当于我们正常写代码,假设每个方法都有异常都要去解决,要么try-catch接收处理异常、要么throw抛出
2、声明式异常
1、是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理
2、在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws 或 @ExceptionHandler),就可以处理特定类型的异常
3、相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。
4、说人话:就是我们把有可能出现的异常,我们写在一个类中,如果程序会出现该异常,就会被该异常方法捕获。
3、站在宏观角度来看待声明式事务处理:
1、整个项目从架构这个层面设计的异常处理的统一机制和规范。
2、一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。
3、使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!
2、基于注解异常声明异常处理
1、思路
- 我们统一用一个类来处理异常,一个异常就对应一个handler方法
2、实现
- 就是用到两个注解
- 一个是放在类上方便的:@RestControllerAdvice
- @RestControllerAdvice = @ResponseBody+@ControllerAdvice
- 另外一个是具体方法上:@ExceptionHandler(xxxx.class【异常类】)
3、例子
1、异常类
package com.atgui.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice // 如果发生异常,会优先走这个类
public class GlobalExceptionHandler {
// 发生异常:
// 指定的异常:可以精确的指定异常,也可以查找父异常
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionHandler(ArithmeticException e){
String message = e.getMessage();
System.out.println("message = " + message);
return message;
}
@ExceptionHandler(Exception.class)
public Object ExceptionHandler(Exception e){
String message = e.getMessage();
System.out.println("message = " + message);
return message;
}
}
2、可能有异常的controller方法
package com.atgui.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("data1")
public void data1() {
int [] res = new int[1];
System.out.println("res = " + res[3]);
}
@GetMapping("data2")
public void data2(){
int a = 1/0;
}
}
2、拦截器
1、什么是拦截器
1、拦截器和过滤器解决问题
-
生活中
为了提高乘车效率,在乘客进入站台前统一检票
-
程序中
在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测
2、拦截器SpringMVC 对比过滤器javaWeb
-
相似点
-
拦截:必须先把请求拦住,才能执行后续操作
-
过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
-
放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
-
-
不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
3、选择
- 功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
2、拦截器的使用
1、步骤
1、先创建一个拦截器
- 创建一个类,
- 这个类实现HandlerInterceptor 接口
- 有3个方法
- preHandle()
- postHandle()
- afterCompletion()
2、在配置类中添加拦截器组件
- 实现这个addInterceptors()方法
2、配置类中的详情
1、默认拦截全部
- 就直接实现这个方法addInterceptors()
2、精准配置拦截
-
设置路径
-
就是在实现这个addInterceptors()方法后面添加一个addPathPatterns()方法
-
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/**");
3、排除拦截
- 设置路径
- 就是在实现这个addPathPatterns()方法后面添加一个方法excludePathPatterns()
3、3个方法介绍
1、preHandle方法
- 作用
- 执行handler之前!调用的拦截器!
- 编码格式设置,登录保护,权限处理
2、postHandle
- 作用
- 执行handler之后,返回modelAndView之前 ,对结果处理,敏感词检查
- 这个方法只能在preHandle方法返回true之后执行
3、afterCompletion方法
- 作用
- 整理处理完毕调用!
4、例子
1、拦截器类
package com.atgui.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
/**
* 执行handler之前!调用的拦截器!
* 编码格式设置,登录保护,权限处理
* @param request 请求对象
* @param response 响应对象
* @param handler 我们handler调用的方法
* @return true是放行,false是拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request = " + request);
return true;
}
/**
* 执行handler之后,返回modelAndView之前 ,对结果处理,敏感词检查
* 这个方法只能在preHandle方法返回true之后执行
* @param request 请求
* @param response 响应
* @param handler handler方法
* @param modelAndView 返回的视图和共享域对象
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
/**
* 整理图例完毕调用!
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler that started asynchronous
* execution, for type and/or instance examination
* @param ex any exception thrown on handler execution, if any; this does not
* include exceptions that have been handled through an exception resolver
* @throws Exception 捕获异常对象
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
2、配置类设置
package com.atgui.config;
import com.atgui.interceptor.MyInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ComponentScan("com.atgui")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 配置方法1:拦截全部请求
// registry.addInterceptor(new MyInterceptor());
// 配置方法2:指定地址拦截.addPathPatterns("/xxx/xxx");
// 也可以模糊:*:任意字符串;**:任意多层字符串
// registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/**");
// 配置方法3:排除拦截 排除的地址应该拦截地址的内部
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/**").excludePathPatterns("/user/data1");
}
}
5、多个拦截器执行的顺序
1、图解

2、执行
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
3、底层代码中,先按List集合来遍历执行preHandle()方法
4、然后再按List集合相反顺序来遍历postHandle() 、postHandle() 、afterCompletion()
3、参数校验
1、在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
2、图解

1、校验概述
1、JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
2、注解
1、基本
| 注解 | 规则 |
|---|---|
| @Null | 标注值必须为 null |
| @NotNull | 标注值不可为 null |
| @AssertTrue | 标注值必须为 true |
| @AssertFalse | 标注值必须为 false |
| @Min(value) | 标注值必须大于或等于 value |
| @Max(value) | 标注值必须小于或等于 value |
| @DecimalMin(value) | 标注值必须大于或等于 value |
| @DecimalMax(value) | 标注值必须小于或等于 value |
| @Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
| @Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
| @Past | 标注值只能用于日期型,且必须是过去的日期 |
| @Future | 标注值只能用于日期型,且必须是将来的日期 |
| @Pattern(value) | 标注值必须符合指定的正则表达式 |
2、扩展
| 标注值必须是格式正确的 Email 地址 | |
| @Length | 标注值字符串大小必须在指定的范围内 |
| @NotEmpty | 标注值字符串不能是空字符串 |
| @Range | 标注值必须在指定的范围内 |
3、补充说明
- Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 @EnableWebMvc 的方式进行数据校验。
- Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。
- Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。
- 配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。
3、操作步骤
1、导入相关依赖
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
2、实体类属性添加校验注解
3、handler方法参数前添加@Validated注解
4、BindingResult对象获取校验错误信息
5、 如果不符合校验郭泽,直接向前端抛出错误
- 接收绑定错误,自定义返回结果
- 捕获错误绑定信息
- 1、handler(校验对象,BindingResult result
- 2、BindingResult result.hasErrors()绑定错误信息
4、例子
1、创建实体类
package com.atgui.pojo;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Data
public class User {
@NotNull
private String name;
@Length(min = 6)
private String password;
@Min(1)
private int age;
@Email
private String email;
@Past
private Data birthDate;
}
2、添加控制器及注解
/*
步骤
1、导入依赖
2、实体类属性添加校验注解
3、handler方法参数前添加@Validated注解
4、BindingResult对象获取校验错误信息
如果不符合校验郭泽,直接向前端抛出错误
接收绑定错误,自定义返回结果
捕获错误绑定信息
1、handler(校验对象,BindingResult result
2、BindingResult result.hasErrors()绑定错误信息
*/
@PostMapping("register")
public Object register(@Validated @RequestBody User user, BindingResult result){
// 设置绑定错误,
if(result.hasErrors()){
Map data = new HashMap();
data.put("code",400);
return data;
}
System.out.println("user = " + user);
return user;
}
5、注意
1、一定要加Validated注解。否则无法实现
2、如果需要抛出错误,就必须实现BindingResult result。紧挨着所需要校验的实体类。


