springMVC源码阅读-解决body不能重复读取问题(十二)

方式一

这种方式线上高并发看链路追踪发现new RequestWrapper耗时3秒 后来改为第二种方式

装饰requset

@Component
@WebFilter(filterName = "wrapperFilter", urlPatterns = {"/**"})
public class WrapperFilter
        implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
    }
}
package cn.wine.ms.promotion.configuartion;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.Objects;

/**
 * @author lau
 * 解决因为ServletInputStream 读取body流 读取后指针不能还原其他地方不能读取多次
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private  byte[] body;

    private Reader reader;

    public RequestWrapper(HttpServletRequest request)
            throws IOException {
        super(request);
        try {
            StringBuffer stringBuffer = new StringBuffer();
            request.getReader().lines().forEach(stringBuffer::append);
            body = stringBuffer.toString().getBytes();
        } catch (IllegalStateException e) {
            body = new byte[]{};
            log.error("{}接口body为空或丢失",request.getRequestURI());
            //部分接口,文件上传,body为空,会报IllegalStateException
        }
    }

    public RequestWrapper(HttpServletRequest request, Reader reader) {
        super(request);
        this.reader = reader;
        StringBuffer stringBuffer = new StringBuffer();
        new BufferedReader(reader).lines().forEach(stringBuffer::append);
        body = stringBuffer.toString().getBytes();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (Objects.isNull(reader)){
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }else {
            return new BufferedReader(reader);
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
}

方式二

package cn.wine.ms.promotion.configuartion;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * @Project 商品uaa
 * @PackageName cn.wine.ms.promotion.configuartion
 * @ClassName RequestMappingHandlerAdapterCusotmer
 * @Author qiang.li
 * @Date 2021/2/23 11:34 上午
 * @Description 用于对springRequestMappingHandlerAdapter做一些定制化配置
 */

@Configuration
public class RequestMappingHandlerAdapterCustomizeConfig implements InitializingBean {
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    @Override
    public void afterPropertiesSet() throws Exception {
        //兼容判断 防止使用非注解方式 如xml配置
        if(requestMappingHandlerAdapter==null){
            return;
        }
        //定制化配置@RequestBody 入参解析器
        replaceRequestResponseBodyMethodProcessor();
    }

    /**
     * 使用RequestResponseBodyMethodProcessorWrapper 替换默认的使用RequestResponseBodyMethodProcessor
     * 解决因为Request.getInputStream()只能读取一次 不能多次读取的问题
     * 1.在原有的基础上增加将解析结果存入Request attributes 供后续使用
     * HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
     * Object data = request.getAttribute(RequestResponseBodyMethodProcessorWrapper.resolveArgumentKey);
     */
    public void replaceRequestResponseBodyMethodProcessor(){
        //获得解析器处理集合
        List<HandlerMethodArgumentResolver> handlerMethodArgumentResolver= requestMappingHandlerAdapter.getArgumentResolvers();
        if(!CollectionUtils.isEmpty(handlerMethodArgumentResolver)){
            Optional<HandlerMethodArgumentResolver> resolverOptional=handlerMethodArgumentResolver.stream().filter(c->c instanceof RequestResponseBodyMethodProcessor).findAny();
            if(resolverOptional.isPresent()){
                //因为框架是不可变的 改为可变类型
                List<HandlerMethodArgumentResolver> newHandlerMethodArgumentResolvers=new ArrayList<>(handlerMethodArgumentResolver);
                //替换为RequestResponseBodyMethodProcessorWrapper
                Collections.replaceAll(newHandlerMethodArgumentResolvers, resolverOptional.get(), new RequestResponseBodyMethodProcessorWrapper(resolverOptional.get()));
                //替换list
                requestMappingHandlerAdapter.setArgumentResolvers(newHandlerMethodArgumentResolvers);
            }
        }
    }
}
package cn.wine.ms.promotion.configuartion;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

/**
 * @Project 商品uaa
 * @PackageName cn.wine.ms.promotion.configuartion
 * @ClassName RequestResponseBodyMethodProcessorWrapper
 * @Author qiang.li
 * @Date 2021/2/23 1:20 下午
 * @Description RequestResponseBodyMethodProcessor装饰器,在原有功能上做将结果保存到request attribute的增加
 */
public class RequestResponseBodyMethodProcessorWrapper implements HandlerMethodArgumentResolver {
    public final static String resolveArgumentKey="resolveArgumentObject";
    private HandlerMethodArgumentResolver handlerMethodReturnValueHandler;
    public RequestResponseBodyMethodProcessorWrapper(HandlerMethodArgumentResolver handlerMethodReturnValueHandler){
        this.handlerMethodReturnValueHandler=handlerMethodReturnValueHandler;
    }


    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        //委托
        return handlerMethodReturnValueHandler.supportsParameter(methodParameter);
    }
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        //委托
        Object argumentObject= handlerMethodReturnValueHandler.resolveArgument(methodParameter,modelAndViewContainer,nativeWebRequest,webDataBinderFactory);
        //功能增强 将解析结果存入request attribute供后续使用
        if(argumentObject!=null){
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            request.setAttribute(resolveArgumentKey,argumentObject);
        }
        return argumentObject;
    }

}

使用 我的场景是异常拦截器打印入参

 /**
     * 获取body
     * @param httpServletRequest
     * @return
     */
    public static String readBody(HttpServletRequest httpServletRequest) {
        try {
            //request body不能重复读取 处理方案请看cn.wine.ms.promotion.configuartion.RequestMappingHandlerAdapterCustomizeConfig
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            Object data = request.getAttribute(RequestResponseBodyMethodProcessorWrapper.resolveArgumentKey);
            return JSON.toJSONString(data);
        }catch (Exception e){
            log.error("获取body数据异常:{}",e);
            return "getError";
        }

    }
原文地址:https://www.cnblogs.com/LQBlog/p/14435922.html