天天看點

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";
        }

    }