解决HttpServletRequest无法获取@RequestBody修饰的参数问题及分析
作者:Spirit_NKlaus
Spring Boot中,@RequestBody读取请求体后流关闭,导致Service层无法获取,解决方案包括创建HTTP工具类、请求体复制包装器及拦截器配置,确保请求体可重复读取
HttpServletRequest无法获取@RequestBody修饰的参数
在使用springboot设计controller时我们通常会在某个请求如post中使用@RequestBody来修饰参数如:
在一些特殊场景下,我们需要在service层的代码去拿到当前上下文请求(HttpServletRequest)中的一些信息如请求体,这个时候被@RequestBody所修饰的请求是无法获取的。
原因如下:
1、请求体流只能读取一次:Servlet 规范中,HttpServletRequest 的输入流 (getInputStream()) 是单向的,一旦被 @RequestBody 读取后,流就会关闭,无法再次读取。
2、@RequestBody 优先处理:Spring MVC 在处理控制器方法时,会先解析 @RequestBody,导致后续通过 HttpServletRequest 获取请求体时为空。
下面简单演示下解决方案:
一、先编写一个http工具类用来读取ServletRequest的内容
import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.List; /** * http工具类 */ @Slf4j public class HttpUtils { /** * 从request获取body的数据 * * @param request 请求 * @return body数据字符串 */ public static String getBodyStr(ServletRequest request) { StringBuilder sb = new StringBuilder(); try { try (ServletInputStream inputStream = request.getInputStream()) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } } } catch (Exception e) { log.error("get request body error: ", e); } return sb.toString(); } /** * 从request获取body的数据,并转换成对象(适用于使用@RequestBody修饰的参数) * * @param request request 请求 * @param clazz 对象类型 * @return 对象 */ public static <T> T getBodyToObject(ServletRequest request, Class<T> clazz) { String bodyStr = getBodyStr(request); if (StringUtils.isBlank(bodyStr)) { return null; } return JSON.parseObject(bodyStr, clazz); } /** * 从request获取body的数据,并转换成集合对象(适用于使用@RequestBody修饰的参数) * * @param request request 请求 * @param clazz 集合对象类型 * @return 集合对象 */ public static <T> List<T> getBodyToList(ServletRequest request, Class<T> clazz) { String bodyStr = getBodyStr(request); if (StringUtils.isBlank(bodyStr)) { return null; } return JSON.parseArray(bodyStr, clazz); } }
二、添加请求体复制包装器
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * 请求体复制包装器 */ public class BodyCopyWrapper extends HttpServletRequestWrapper { private byte[] requestBody; public BodyCopyWrapper(HttpServletRequest request) throws IOException { super(request); requestBody = HttpUtils.getBodyStr(request).getBytes(StandardCharsets.UTF_8); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream basis = new ByteArrayInputStream(requestBody); return new ServletInputStream() { @Override public int read() throws IOException { return basis.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } public void setInputStream(byte[] body) { this.requestBody = body; } }
三、添加请求拦截器配置类
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 请求拦截器配置类 */ @Slf4j @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<RequestWrapperFilter> requestWrapperFilter() { FilterRegistrationBean<RequestWrapperFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new RequestWrapperFilter()); bean.addUrlPatterns("/*"); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } public static class RequestWrapperFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException , IOException { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new BodyCopyWrapper((HttpServletRequest) request); } if (null == requestWrapper) { log.warn("未进行request包装返回原来的request"); chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } } }
四、业务代码中使用方式
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); //转对象 UserLoginDTO userLoginDTO = HttpUtils.getBodyToObject(request, UserLoginDTO.class); //直接获取内容字符串 String bodyStr = HttpUtils.getBodyStr(request);
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。