浅出:Spring Boot 拦截器如何优雅地返回 JSON 数据
在现代 Web 开发中,拦截器(Interceptor)是处理请求前、请求后以及完成请求后逻辑的强大工具,我们经常使用拦截器进行用户身份验证、权限校验、日志记录等操作,在这些场景中,当校验失败时(用户未登录或权限不足),我们通常希望直接拦截请求,并向前端返回一个结构化的 JSON 错误信息,而不是跳转到错误页面或返回一个 HTML 页面。
如何在 Spring Boot 的拦截器中实现“返回 JSON”这一功能呢?本文将详细介绍几种常用且优雅的实现方式。
核心思路:打破拦截器的“链式”执行
默认情况下,拦截器通过 preHandle 方法的返回值来控制是否继续执行后续的拦截器和目标 Controller。preHandle 返回 true 时,继续执行;返回 false 时,拦截链中断,请求不会到达 Controller。
仅仅返回 false 是不够的,这只会让请求“石沉大海”,前端收不到任何响应,我们的目标是:在 preHandle 中返回 false 的同时,向前端发送一个明确的 JSON 响应。
实现这一目标的关键在于,我们需要在拦截器内部手动构建并发送 HTTP 响应。
使用 HttpServletResponse 直接写入(最直接)
这是最基础也是最直接的方法,在拦截器的 preHandle 方法中,我们可以获取到 HttpServletResponse 对象,然后通过它直接向输出流写入 JSON 数据。
实现步骤:
- 获取
HttpServletResponse:preHandle方法会传入HttpServletRequest,HttpServletResponse, 和Object handler。 - 设置响应头:告诉浏览器响应体的内容格式是 JSON。
- 获取输出流:使用
response.getWriter()或response.getOutputStream()。 - 写入 JSON 字符串:将你的 JSON 数据写入输出流。
- 返回
false:阻止请求继续传递。
代码示例:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class AuthInterceptor implements HandlerInterceptor {
// 注入 ObjectMapper,用于对象转 JSON
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 模拟权限校验逻辑,例如检查请求头中是否有 token
String token = request.getHeader("X-Token");
if (token == null || !token.equals("valid-token")) {
// 2. 校验失败,构建错误信息
Map<String, Object> result = new HashMap<>();
result.put("code", 401); // HTTP 状态码或自定义业务码
result.put("message", "未授权访问,请先登录");
result.put("data", null);
// 3. 设置响应内容为 JSON
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 设置 HTTP 状态码
// 4. 将结果写入响应流
try {
response.getWriter().write(objectMapper.writeValueAsString(result));
} catch (IOException e) {
e.printStackTrace();
}
// 5. 返回 false,阻止后续流程
return false;
}
// 校验通过,继续执行
return true;
}
}
优点:
- 简单直观,不依赖额外框架。
- 逻辑完全在拦截器内部,易于理解。
缺点:
- 每次手动处理 JSON 序列化和响应流,代码略显冗余。
- 如果多处需要返回 JSON,会产生重复代码。
结合全局异常处理(推荐,更优雅)
方法一虽然可行,但在实际项目中,我们更推崇“关注点分离”的原则,拦截器负责“拦截”和“判断”,而“返回错误响应”这个行为,可以交给一个统一的“全局异常处理器”来处理。
核心思路:
在拦截器中,我们不直接写 JSON,而是抛出一个自定义的异常,通过 @ControllerAdvice 和 @ExceptionHandler 注解,捕获这个异常并统一处理成 JSON 响应。
实现步骤:
-
创建自定义异常类:
public class NoAuthException extends RuntimeException { public NoAuthException(String message) { super(message); } } -
修改拦截器,抛出异常:
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("X-Token"); if (token == null || !token.equals("valid-token")) { // 抛出自定义异常,而不是直接写响应 throw new NoAuthException("未授权访问,请先登录"); } return true; } } -
创建全局异常处理器:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.HashMap; import java.util.Map; @RestControllerAdvice // 这个注解会使其成为全局异常处理器 public class GlobalExceptionHandler { @ExceptionHandler(NoAuthException.class) // 捕获 NoAuthException public ResponseEntity<Map<String, Object>> handleNoAuthException(NoAuthException ex) { Map<String, Object> body = new HashMap<>(); body.put("code", 401); body.put("message", ex.getMessage()); body.put("data", null); // 返回 ResponseEntity,可以更灵活地设置状态码和 body return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body); } // 可以继续添加其他异常的处理... }
优点:
- 代码优雅且解耦:拦截器只负责业务逻辑判断,异常处理被统一管理。
- 高度可维护:所有错误响应的格式、状态码都在一个地方定义,修改方便。
- 功能强大:
@RestControllerAdvice可以处理 Controller 层抛出的几乎所有异常,不仅仅是拦截器中的。
缺点:
- 需要额外定义异常类和全局处理器,对于非常简单的项目可能略显“重”。
使用 ResponseBodyAdvice(终极优雅)
ResponseBodyAdvice 是 Spring 提供的一个强大接口,它允许我们在 Controller 方法返回的响应体(@ResponseBody 或 @RestController)被写入 HTTP 响应之前,对其进行统一的修改。
我们可以利用它来统一处理所有返回值,将其包装成我们想要的 JSON 格式,包括拦截器中返回的 false 情况。
实现步骤:
-
创建一个统一的响应包装类:
public class Result<T> { private Integer code; private String message; private T data; // 构造器、Getter 和 Setter // ... } -
创建
ResponseBodyAdvice实现:import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @ControllerAdvice public class CommonResponseBodyAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) { // 对所有 Controller 的返回方法都生效 return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, org.springframework.http.server.ServerHttpRequest request, org.springframework.http.server.ServerHttpResponse response) { // 如果返回值已经是 Result 类型,则不再包装 if (body instanceof Result) { return body; } // 否则,用 Result 包装一下 return new Result<>(200, "success", body); } } -
修改拦截器,使用
ModelAndView返回错误信息: 这种方式比较巧妙,我们可以利用 Spring MVC 的ModelAndView机制,拦截器返回false时,我们可以设置一个ModelAndView,其视图名指向一个专门处理错误的逻辑,这个逻辑会触发ResponseBodyAdvice。@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("X-Token"); if (token == null || !token.equals("valid-token")) { // 创建一个 ModelAndView,并设置一个特殊属性 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("error/no_auth



还没有评论,来说两句吧...