zoukankan      html  css  js  c++  java
  • 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";
            }
    
        }
  • 相关阅读:
    playbook配置不同系统版本的yum源配置
    使用playbook部署lamp
    ansible常用模块
    部署Ansible
    Ansible介绍与安装
    lamp
    mysql主从
    mysql进阶命令
    mysql基础命令
    Dockerfile制作http镜像(以alpine做底层镜像为例)
  • 原文地址:https://www.cnblogs.com/LQBlog/p/14435922.html
Copyright © 2011-2022 走看看