一. 拦截器
我们在学习写强制登录时,后端程序根据需要根据 Session 来判断用户是否登录, 但是实现方法是比较麻烦的---需要修改每个接⼝的处理逻辑 ;需要修改每个接⼝的返回结果 ;接口定义修改,前端代码也需要跟着修改
有没有更简单的办法, 统⼀拦截所有的请求, 并进行 Session 校验呢, 这立我们学习⼀种新的解决办法: 拦截器
1.1 什么是拦截器
拦截器是在请求 / 数据抵达目标处理逻辑前、后自动拦截并执行通用处理(如登录校验、日志记录)的非侵入式代码组件,像业务流程中的统一关卡。
拦截器是Spring框架提供的核心功能之⼀,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码

无拦截器:请求---> controller---> service ......
请求 → 拦截器前置 → Controller → Service → 拦截器后置 → 拦截器完成 → 响应
总结:拦截器:拦截用户请求,在目标方法前后,执行的一段代码(由程序员自定义)
1.2 拦截器的使用
拦截器的使用步骤分为两步:
- 定义拦截器
- 注册配置拦截器
1.2.3 自定义拦截器
实现HandlerInterceptor接口,并重写其所有方法
java
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override//前置处理(目标方法执行前执行)
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
return true;//返回 true:放行(执行后续程序)
//返回 false 拦截(直接终止整个请求流程)
}
@Override//后置处理(目标方法执行后执行)
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
}
@Override//视图渲染后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");
}
}
HandlerInterceptor接口 中的三个方法:
| 方法名 | 执行时机 | 返回值 / 核心行为 |
|---|---|---|
preHandle |
目标方法(Controller 接口)执行前 | ✅ 返回true:继续执行后续操作(Controller 等);✅ 返回false:中断后续所有操作 |
postHandle |
Controller 方法执行后 、响应返回前 | ❌ 无返回值;仅执行方法内逻辑,不影响流程走向 |
afterCompletion |
响应已返回给客户端后(无论是否异常) | ❌ 无返回值;整个请求流程的最后一步,仅执行方法内逻辑 |
1.2.3 注册配置拦截器
实现WebMvcConfigurer接口,并重写addInterceptors方法
java
@Configuration
public class webConfig implements WebMvcConfigurer {
@Autowired
LoginInterceptor loginInterceptor;//注入⾃定义的拦截器对象
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)//注册⾃定义拦截器对象
.addPathPatterns("/**")//要拦截的路径
.excludePathPatterns("/user/login","/html/**","/css/**","/js/**");//排除拦截的路径
}
}
1.3 拦截器详细
1.3.1 拦截路径
拦截路径 是指我们定义的这个拦截器 , 对哪些请求生效 . 我们在注册配置拦截器的时候,通过addPathPatterns() 方法指定要拦截哪些请求. 也可以通过 excludePathPatterns() 指定不拦截哪些请求. 上述代码中,我们配置的是 /** ,表式拦截所有的请求.
在拦截器中除了可以设置 /** 拦截所有资源外,还有⼀些常见拦截路径设置:
| 拦截路径 | 含义 | 匹配示例(能匹配) | 不匹配示例 |
|---|---|---|---|
/* |
一级路径 | /user、/book、/login |
/user/login、/book/1 |
/** |
任意级路径 | /user、/user/login、/user/reg |
无(所有路径都能匹配) |
/book/* |
/book 下的一级路径 | /book/addBook、/book/delete |
/book、/book/addBook/1 |
/book/** |
/book 下的任意级路径 | /book、/book/addBook、/book/addBook/2 |
/user/login、/order/1 |
以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图片文件, JS 和 CSS 等文件).
1.3.2 拦截器执行流程
正常的调用顺序:

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

-
添加拦截器后, 执行Controller的方法之前,请求会先被拦截器拦截住.执行 preHandle() 方法, 这个方法需要返回⼀个布尔类型的值. 如果返回true, 就表示放行本次操作,继续访问controller中的方法. 如果返回false,则不会放行(controller中的方法也不会执行).
-
controller当中的方法执行完毕后,再回过来执行 postHandle() 这个方法以及 afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据.
1.4 登录校验
1.4.1 自定义拦截器
java
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override//前置处理(目标方法执行前执行)
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session =request.getSession();
UserInfo userInfo=(UserInfo) session.getAttribute("SESSION_USER_KEY");
if(userInfo!=null){
log.info("登录成功");
return true;
}
response.setStatus(401);
return false;
}
}
1.4.2 注册配置拦截器
java
@Configuration
public class webConfig implements WebMvcConfigurer {
@Autowired
LoginInterceptor loginInterceptor;//注入⾃定义的拦截器对象
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)//注册⾃定义拦截器对象
.addPathPatterns("/**")//要拦截的路径
.excludePathPatterns("/user/login","/html/**","/css/**","/js/**");//排除拦截的路径
}
}
二. 统⼀数据返回格式
强制登录案例中,我们共做了两部分工作:
-
通过Session来判断用户是否登录
-
对后端返回数据进行封装,告知前端处理的结果
回顾
后端统⼀返回结果
java@Data public class Result { private int status; private String errorMessage; private T data; }后端逻辑处理
java@RequestMapping("/getListByPage") public Result getListByPage(PageRequest pageRequest) { log.info("获取图书列表, pageRequest:{}", pageRequest); //⽤⼾登录, 返回图书列表 PageResult pageResult = bookService.getBookListByPage(pageRequest); log.info("获取图书列表222, pageRequest:{}", pageResult); return Result.success(pageResult); }
拦截器帮我们实现了第一个功能,接下来看SpringBoot对第二个功能如何支持
统⼀的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现
2.1 快速上手
添加 @ControllerAdvice 注解,并实现 ResponseBodyAdvice
java
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;//返回 true :处理返回数据格式
//返回 false:不处理返回数据格式
}
@SneakyThrows//该注解会给这整个方法都加上 try catch
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//body 为目标方法返回的结果
if (body instanceof Result<?>){
return body;
}
//统一结果返回, 需要对String类型额外处理,使⽤SpringBoot内置提供的Jackson来实现信息的序列化
if (body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);//将数据统一封装成result类型
}
}
注意:
- 统⼀数据返回格式时,当返回数据类型为 String 类型时 需要额外处理
- supports方法: 判断是否要执行 beforeBodyWrite方法. true为执行, false不执行 . 通过该方法可以 选择哪些类或哪些方法的response要进行处理, 其他的不进行处理.
三. 统⼀异常处理
统⼀异常处理使的是@ControllerAdvice +@ExceptionHandler 来实现的, @ControllerAdvice 表式控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件
java
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object hand(Exception e){
return Result.fail(e.getMessage());
}
}
以上代码表式,如果代码出现Exception异常(包括Exception的子类), 返回⼀个 Result 的对象, 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;
}
我们可以针对不同的异常,返回不同的结果.
java
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object hand(Exception e){
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handler(NullPointerException e) {
return Result.fail("发⽣NullPointerException:"+e.getMessage());
}
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Result.fail("发⽣ArithmeticException:"+e.getMessage());
}
}
注意:
- 当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配
- 同一异常处理和统一数据格式处理都会执行,但有明确的执行顺序
🚦 执行顺序(关键)
- 请求进入 Controller 方法
正常业务:方法执行完毕,返回数据。
业务异常:方法抛出异常,中断执行。
- 异常处理阶段( @RestControllerAdvice + @ExceptionHandler )
如果 Controller 抛出异常,Spring 会先被 统一异常处理器 捕获。
异常处理器会将异常信息封装为统一的 Result 对象(例如 Result.error(500, "系统错误") )并返回。
如果没有异常,直接进入下一步。
- 数据封装阶段( ResponseBodyAdvice )
无论是 Controller 正常返回的数据,还是异常处理器返回的 Result 对象,都会进入 beforeBodyWrite 方法。
你的代码会判断:
如果已经是 Result 类型 → 直接返回(避免重复封装)。
如果是其他类型 → 包装成 Result.success(...) 。
- 如果在controller层已经把异常处理了,统⼀异常处理就不会执行