目录
一、拦截器:统一处理通用业务逻辑
在大家刚刚学习spring过程中,尤其是写些带有用户强制登录的案例中,肯定遇到不少麻烦,包括于但不仅限于:需要修改每个接⼝的处理逻辑,需要修改每个接⼝的返回结果 ,接⼝定义修改,前端代码也需要跟着修改,苦不堪言,代码冗余就算了,开发的效率也大大折扣,这就需要引入拦截器了
什么是拦截器
拦截器是 SpringMVC 的核心组件,能在请求生命周期的关键节点执行自定义逻辑,常用于登录校验、接口访问日志、权限控制、参数校验等场景
也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以在⽤⼾请求前阻⽌其执⾏.
在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通⽤性的操作,⽐如通过拦截器来拦截前端发来的请求,判断Session中是否有登录⽤⼾的信息.如果有就可以放⾏,如果没有就进⾏拦截

拦截器的基本使⽤
拦截器的使⽤步骤分为两步:
- 定义拦截器
- 注册配置拦截器
定义拦截器
实现HandlerInterceptor接⼝,并重写其所有⽅法
java
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle目标方法执行前");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion");
}
}
- preHandle():在目标 Controller 方法执行之前被调用,如果是
true就" 放行 " (类似于执行controller方法),如果是false就"拦死"(不允许进去controller方法)
图中代码就通过session验证用户是否已经登录,如果没有登录就返回false,无法进如controller方法中
- postHandle()⽅法:⽬标⽅法执⾏后执⾏(于preHandle()相反,执行完controller后拦截)
- afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图,暂不了解)
注册配置拦截器
实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
LoginInterceptor loginInterceptor;
//注册⾃定义拦截器对象
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");
//设置拦截器拦截的请求路径( /**表⽰拦截所有请求)
}
}
执行查询图书id的请求观察后端⽇志

可以看到preHandle⽅法执⾏之后就放⾏了,开始执⾏⽬标⽅法,⽬标⽅法执⾏完成之后执⾏postHandle和afterCompletion⽅法.
拦截器详解
拦截器的拦截路径配置
addPathPatterns()
在上述代码中,我们通过addPathPatterns() 配置 /**路径表示拦截所有的请求,所以addPathPatterns() 表示⽅法指定要拦截哪些请求
excludePathPatterns()
有指定拦截哪些请求,也有excludePathPatterns() 指定不拦截哪些请求
拦截路径设置说明
| 拦截路径 | 含义 | 举例 |
|---|---|---|
| /* | ⼀级路径 | 能匹配/user,/book,/login,不能匹配/user/login |
| /** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
| /book/* | /book下的⼀级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,/book |
| /book/** | /book下的任意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |
拦截器执⾏流程
正常的调⽤顺序:

有了拦截器之后,会在调⽤Controller之前进⾏相应的业务处理,执⾏的流程如下图:

- 添加拦截器后,执⾏Controller的⽅法之前,请求会先被拦截器拦截住.执⾏
preHandle()⽅法,这个⽅法需要返回⼀个布尔类型的值.如果返回true,就表⽰放⾏本次操作,继续访问controller中的⽅法.如果返回false,则不会放⾏(controller中的⽅法也不会执⾏) - controller当中的⽅法执⾏完毕后,再回过来执⾏
postHandle()这个⽅法以及afterCompletion()⽅法,执⾏完毕之后,最终给浏览器响应数据
二、统一数据返回格式:告别杂乱的返回值
刚刚通过学会了利用拦截器来统一处理通用业务逻辑,问题又来了,比如在我们使用前端对接时要适配多种格式,极易出 bug;后端排查问题时也无法快速定位(比如不知道是 "参数错" 还是 "服务器错"),同样的苦不堪言,这就需要用到统⼀数据返回格式了,让我们告别杂乱的返回值
什么是统一数据返回格式
统一数据返回格式,就是让 Spring Boot 项目中所有接口 (无论成功 / 失败、有数据 / 无数据)返回的 JSON 结构完全一致,字段固定、含义明确。
统一数据返回格式的基本使⽤
统⼀的数据返回格式使⽤@ControllerAdvice 和ResponseBodyAdvice 的⽅式实现
@ControllerAdvice 表⽰控制器通知类添加类 ResponseAdvice ,实现@ControllerAdvice 注解
java
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//false 不处理 true 处理
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
- supports⽅法 : 判断是否要执⾏
beforeBodyWrite⽅法.true 为执⾏,false 不执⾏. 通过该⽅法可以选择哪些类或哪些⽅法的response要进⾏处理,其他的不进⾏处理 - beforeBodyWrite⽅法 : 对response⽅法进⾏具体操作处理
测试查询图书id的请求
添加统⼀数据返回格式之前:

添加统⼀数据返回格式之后:

使用String类型存在问题
接口代码:
java
@RequestMapping(value = "/deleteBook",produces = "application/json")
public String deleteBook(Integer bookId){
log.info("删除图书,bookid: {}",bookId);
try {
BookInfo bookInfo=new BookInfo();
bookInfo.setId(bookId);
bookInfo.setStatus(BookStatusEnum.DELETED.getCode());
bookService.updateBook(bookInfo);
//成功
return "";
} catch (Exception e){
log.error("修改图书发生异常,e",e);
return "修改图书发生异常";
}
}
当我们执行增加图书的接口
测试结果:

查看一下日志

这个日志显示的是类型转换异常(ClassCastException)
多测试几种返回类型,发现只要返回结果为String类型时才有这种错误发⽣,原因是接口返回了String类型的数据 ,全局响应包装(如GlobalResponseAdvice)将其自动包装为Result对象 ,但 Spring 的StringHttpMessageConverter会优先处理 String 类型的返回值,试图将包装后的Result强制转换为String,从而触发类型转换异常
解决办法
则将其包装为统一的 Result 格式对象 ,再通过 ObjectMapper 序列化为标准 JSON 字符串返回
java
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果为string得进行封装 对于string类型,前端接收不到,所有全部给改成json类似,加producee="application/json
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
测试接口结果:


三、统一异常处理
什么是统一异常处理
统一异常处理实现的全局异常捕获机制 ,能集中捕获项目中所有接口抛出的异常(业务异常、参数异常、系统异常等),并将异常信息封装成和成功响应一致的统一格式(如之前的 Result 类)返回给前端,替代零散的 try-catch 处理
统一异常处理的基本使用
统⼀异常处理使⽤的是@ControllerAdvice +@ExceptionHandler
来实现的, @ControllerAdvice 表⽰控制器通知类,@ExceptionHandler 是异常处理器,两个结合表⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件
java
@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler
public Result handler(Exception e) {
log.error("发生异常,e",e);
return Result.fail("内容异常,请联系管理员");
}
以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类),就返回⼀个Result的对象.对象的设置参考Result.fail(e.getMessage())
java
public static Result fail(String msg) {
Result result = new Result();
result.setStatus(ResultStatus.FAIL);
result.setErrorMessage(msg);
result.setData("");
return result;
}
这边模拟制造异常:当添加统⼀数据返回格式使用String类型存在问题的返回:

其他说明:
类名,⽅法名和返回值可以⾃定义,重要的是注解
@RestControllerAdvice=@ControllerAdvice +@ResponseBody
当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配
