如何通过注解为指定Controller方法缓存请求InputStream且不影响同路由端点?
解决方案:基于注解的请求负载缓存实现
1. 定义自定义缓存注解
先创建一个标记型注解,用来标识需要缓存请求体的控制器方法:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CacheRequestBody { }
2. 实现请求流缓存的请求包装类
自定义HttpServletRequestWrapper子类,将输入流缓存到字节数组中,实现重复读取:
public class CachedHttpServletRequest extends HttpServletRequestWrapper { private final byte[] cachedBody; public CachedHttpServletRequest(HttpServletRequest request) throws IOException { super(request); this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public byte[] getCachedBody() { return this.cachedBody; } }
3. 实现拦截器识别注解并缓存请求
在拦截器中判断当前请求对应的控制器方法是否带有目标注解,若有则替换请求为缓存包装类:
public class RequestBodyCacheInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; CacheRequestBody annotation = handlerMethod.getMethodAnnotation(CacheRequestBody.class); if (annotation != null) { CachedHttpServletRequest cachedRequest = new CachedHttpServletRequest(request); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(cachedRequest)); } } return true; } }
4. 注册拦截器
通过配置类将拦截器加入Spring MVC拦截链:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestBodyCacheInterceptor()) .addPathPatterns("/**"); } }
5. 控制器方法中使用注解
在需要缓存请求体的方法上添加注解,同路由的其他方法不加则不会触发缓存:
@RestController @RequestMapping("/api") public class DemoController { @PostMapping("/data") @CacheRequestBody public ResponseEntity<String> cachedEndpoint(@RequestBody String requestBody) { // 可重复读取缓存的请求体 return ResponseEntity.ok("Cached: " + requestBody); } @PostMapping("/data") public ResponseEntity<String> normalEndpoint(@RequestBody String requestBody) { // 不触发缓存,保持原有逻辑 return ResponseEntity.ok("Normal: " + requestBody); } }
核心说明
- 拦截器可以通过
HandlerMethod直接获取控制器方法的注解,不存在无法识别的问题,之前的误区是未正确将包装后的请求替换到上下文。 - 通过
HttpServletRequestWrapper包装请求,既实现了输入流缓存,又不影响原有请求的读取逻辑。 - 同路由下的不同方法可通过注解精准区分,完全不影响未标注注解的端点。
内容的提问来源于stack exchange,提问作者NOOBAF
相关产品推荐
相关产品推荐

