一、拦截器基础
在开发中,我们常遇到需要强制登录校验的场景。如果直接在每个 Controller 方法里写 Session 判断,不仅代码冗余,一旦逻辑变更,所有接口都得改,前端也得跟着调整。有没有更优雅的方式?答案是:拦截器。
拦截器是 Spring 提供的非侵入式组件,能在请求抵达目标逻辑前或后自动执行通用处理(如鉴权、日志)。它就像业务流程中的关卡,无需修改业务代码即可控制请求流向。

没有拦截器时,请求直接进 Controller;有了拦截器,流程变为:请求 → 拦截器前置 → Controller → Service → 拦截器后置 → 完成 → 响应。
1.1 核心生命周期
实现 HandlerInterceptor 接口并重写三个关键方法:
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行前,返回 true 放行,false 中断
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor 目标方法执行前执行..");
return true;
}
// Controller 执行后,视图渲染前
@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 视图渲染完毕后执行,最后执行");
}
}
| 方法名 | 执行时机 | 返回值 / 核心行为 |
|---|---|---|
preHandle | Controller 执行前 | 返回 true 继续,false 终止 |
postHandle | Controller 执行后 | 无返回值,仅做逻辑处理 |
afterCompletion | 响应返回后 | 无返回值,清理资源或记录日志 |
1.2 注册配置
定义好拦截器后,需通过 WebMvcConfigurer 进行注册。这里要注意路径匹配规则,避免误伤静态资源。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/user/login", "/html/**", "/css/**", "/js/**"); // 排除登录页和静态资源
}
}
路径匹配说明
/*:一级路径,如/user匹配,但/user/login不匹配。/**:任意级路径,匹配所有子路径。/book/*:/book下的一级路径。/book/**:/book下的所有子路径。
1.3 实战:登录校验
结合 Session 实现具体的登录拦截逻辑。注意,这里只需关注 preHandle,因为后续步骤通常不需要阻断。
@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;
}
}
二、统一数据返回格式
除了拦截,后端还常需要统一返回给前端的 JSON 结构(例如统一的 Result<T> 对象)。手动在每个 Controller 返回时封装很繁琐,Spring Boot 提供了 @ControllerAdvice + ResponseBodyAdvice 组合拳。
2.1 快速上手
添加注解并实现接口,重点处理 String 类型的序列化问题。
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
// 判断是否处理该方法的返回值
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
// 写入响应体之前调用
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果已经是 Result 类型,直接返回,避免重复封装
if (body instanceof Result<?>) {
return body;
}
// String 类型需要特殊处理,否则会被当作普通字符串而非 JSON
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.success(body));
}
// 其他类型统一包装
return Result.success(body);
}
}
注意: 当返回类型为 String 时,必须手动序列化,否则前端收到的可能是一个包裹了引号的字符串,而不是 JSON 对象。
三、统一异常处理
业务逻辑难免出错,与其让浏览器显示堆栈信息,不如统一捕获异常并返回友好的错误提示。使用 @ControllerAdvice 配合 @ExceptionHandler 即可。
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
// 捕获所有 Exception
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) {
return Result.fail(e.getMessage());
}
// 针对特定异常自定义处理
@ExceptionHandler(NullPointerException.class)
public Object handleNPE(NullPointerException e) {
return Result.fail("发生空指针异常:" + e.getMessage());
}
}
3.1 执行顺序关键点
理解这三者的执行顺序对调试至关重要:
- 请求进入:先经过拦截器的
preHandle。 - 业务执行:Controller 方法运行。若抛出异常,直接进入异常处理阶段;若正常,返回数据。
- 异常捕获:
@ExceptionHandler优先于ResponseBodyAdvice捕获异常。如果 Controller 内部已 try-catch 处理,则不会触发全局异常处理器。 - 数据封装:无论是正常返回的数据,还是异常处理器返回的
Result对象,都会进入ResponseBodyAdvice.beforeBodyWrite。因此,我们在beforeBodyWrite中要判断是否已是Result类型,防止二次封装。
这样一套组合下来,既保证了接口的安全性,又确保了前后端交互格式的规范性。


