记一次切面中读取请求体报错 Cannot call getReader()

问题

写了一个切面来处理被指定自定义注解标注的方法:

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class MyAnnoAspect {

    private final HttpServletRequest request;

    @Around("@annotation(myAnno)")
    public Object handleMyAnno(ProceedingJoinPoint joinPoint, MyAnno myAnno) throws Throwable {
        String body = ServletUtil.getBody(request);
        // ...
        // 继续执行原方法
        return joinPoint.proceed(args);
    }
}

其中使用ServletUtil.getBody(request)来读取请求体。

测试发现,当 Controller 方法使用@RequestBody标注方法参数时。切面中获取请求体会报错UT010004: Cannot call getReader(), getInputStream() already called。因为request.getReader()已经在@RequestBody的处理逻辑中调用过了,不能重复调用。

解决

ContentCachingRequestWrapper是 Spring 提供的一个请求包装器,可以缓存请求体。

可以在 Servlet 过滤器中使用ContentCachingRequestWrapper包装原有请求,使请求体可以被多次读取。

先新建过滤器使用ContentCachingRequestWrapper来包装请求:

public class CustomRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        filterChain.doFilter(wrappedRequest, response);
    }
}

然后注册过滤器:

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<CustomRequestFilter> customFilter() {
        FilterRegistrationBean<CustomRequestFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CustomRequestFilter());
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}

其中OncePerRequestFilter本身实现了javax.servlet.Filter接口,所以实际上还是基于 Servlet 过滤器机制。

FilterRegistrationBean是 Spring Boot 提供的一个组件,允许开发者以编程方式注册和配置过滤器。

posted @ 2024-11-16 11:38  Higurashi-kagome  阅读(183)  评论(0)    收藏  举报