拦截器如何返回JSON:实现原理与最佳实践
在Java Web开发中,拦截器(Interceptor)是AOP(面向切面编程)思想的典型应用,常用于处理请求的前置逻辑(如权限校验、参数校验)、后置逻辑(如响应封装、日志记录)等场景,当需要通过拦截器统一返回JSON格式数据时(例如权限校验失败时返回{"code":403,"message":"无权限"}
),就需要拦截器的响应处理机制,本文将结合主流框架(Spring MVC、Spring Boot),详细讲解拦截器返回JSON的实现原理与具体方法。
拦截器返回JSON的核心原理
拦截器本质上是一个实现了HandlerInterceptor
接口的组件,其执行流程围绕DispatcherServlet
的请求处理过程:请求到达Controller之前会先经过拦截器的preHandle
方法,Controller处理完成后会经过postHandle
和afterCompletion
方法,要实现拦截器返回JSON,关键在于在拦截器中直接向响应输出流写入JSON数据,并阻止后续流程执行。
核心逻辑:
- 获取响应对象:通过
HttpServletRequest
获取HttpServletResponse
对象,用于操作响应内容。 - 设置响应头:明确告知客户端返回的数据格式为JSON(如
Content-Type: application/json
)。 - 写入JSON数据:通过
PrintWriter
或OutputStream
将JSON字符串写入响应输出流。 - 终止后续流程:通过
return false
(preHandle
方法)或response.flushBuffer()
(其他方法)阻止请求继续传递到Controller或后续拦截器。
Spring MVC/Boot 中拦截器返回JSON的实践
拦截器基础实现
首先定义一个拦截器,实现HandlerInterceptor
接口,以下以Spring Boot为例,展示一个简单的权限校验拦截器,当用户未登录时返回JSON错误信息。
(1)定义拦截器
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.util.HashMap; import java.util.Map; @Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 模拟从session中获取用户信息 String userId = (String) request.getSession().getAttribute("userId"); if (userId == null) { // 用户未登录,需要返回JSON Map<String, Object> result = new HashMap<>(); result.put("code", 401); result.put("message", "用户未登录,请先登录"); result.put("data", null); // 使用Jackson将Map转为JSON字符串 ObjectMapper objectMapper = new ObjectMapper(); String jsonResult = objectMapper.writeValueAsString(result); // 设置响应头 response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 获取输出流并写入JSON数据 response.getWriter().write(jsonResult); // 终止后续流程(关键!) return false; } // 用户已登录,放行 return true; } }
(2)注册拦截器
在Spring Boot中,通过实现WebMvcConfigurer
接口注册拦截器,并指定拦截路径:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/**") // 拦截所有/api/*路径 .excludePathPatterns("/api/login", "/api/register"); // 排除登录、注册接口 } }
关键代码解析
(1)response.setContentType("application/json;charset=UTF-8")
- 作用:设置响应内容类型为JSON,并指定字符集为UTF-8,避免中文乱码。
- 替代方案:若使用Spring的
HttpServletResponse
,也可通过response.setHeader("Content-Type", "application/json;charset=UTF-8")
设置。
(2)ObjectMapper.writeValueAsString(result)
- 作用:使用Jackson库将Java对象(如
Map
、自定义实体类)序列化为JSON字符串,需引入依赖:<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency>
(3)response.getWriter().write(jsonResult)
- 作用:获取响应的字符输出流,写入JSON字符串,注意:若响应数据较大(如文件下载场景),建议使用
OutputStream
(response.getOutputStream()
)。
(4)return false
(preHandle
方法)
- 作用:
preHandle
方法的返回值决定是否放行请求:true
表示继续流程(调用Controller),false
表示中断流程,直接返回响应,在拦截器中返回JSON后,必须返回false
,否则请求仍会传递到Controller。
其他场景:postHandle
或afterCompletion
中返回JSON
虽然preHandle
是最常用的拦截器返回JSON的场景,但在某些复杂场景(如统一异常处理、响应数据包装)中,也可能在postHandle
或afterCompletion
中返回JSON,由于Controller已执行完毕,需通过response.reset()
清空缓冲区(避免与Controller的响应冲突),再写入JSON数据。
示例:postHandle
中返回JSON
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 模拟Controller执行后,统一包装响应数据 if (modelAndView != null && modelAndView.getModel().containsKey("data")) { Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("message", "success"); result.put("data", modelAndView.getModel().get("data")); ObjectMapper objectMapper = new ObjectMapper(); String jsonResult = objectMapper.writeValueAsString(result); // 清空缓冲区(关键!避免与Controller的视图渲染冲突) response.reset(); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(jsonResult); } }
常见问题与解决方案
中文乱码问题
现象:返回的JSON中中文显示为"message":"\u7528\u6237\u672a\u767b\u5f55"
。
原因:响应未正确设置字符集(如默认使用ISO-8859-1)。
解决:确保调用response.setContentType("application/json;charset=UTF-8")
,或在Spring配置中统一设置字符编码:
@Configuration public class WebConfig implements WebMvcConfigurer { @Bean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); filter.setForceEncoding(true); return filter; } }
响应数据未生效,仍返回Controller的响应
现象:拦截器中写入JSON后,客户端仍收到Controller返回的数据(如HTML页面)。
原因:拦截器未正确终止流程(如preHandle
中忘记返回false
),或postHandle
中未调用response.reset()
导致缓冲区数据冲突。
解决:
- 在
preHandle
中返回JSON后,务必返回false
; - 在
postHandle
/afterCompletion
中返回JSON前,调用response.reset()
清空缓冲区。
多个拦截器时的执行顺序
现象:多个拦截器同时存在时,JSON返回顺序与预期不符。
原因:拦截器注册顺序决定了执行顺序(先注册的preHandle
先执行,后注册的postHandle
先执行)。
解决:通过InterceptorRegistry
的addInterceptor
顺序控制,确保需要返回JSON的拦截器优先执行或在正确的位置中断流程。
最佳实践
统一使用全局异常处理+拦截器组合
对于需要统一返回JSON的场景(如权限校验、参数校验),推荐将拦截器与@ControllerAdvice
结合:
- 拦截器负责校验逻辑,若校验失败,抛出自定义异常(如
AuthException
); @ControllerAdvice
捕获异常,统一封装为JSON返回。
优点:职责分离,代码更清晰,避免拦截器中直接处理响应逻辑。
示例:
// 自定义异常 public class AuthException extends RuntimeException { public AuthException(String message) { super(message); } } // 拦截器中抛出异常 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse
还没有评论,来说两句吧...