HttpServletRequestWrapper,Filter 和 RequestBodyAdviceAdapter以及请求进入到servlet容器中执行流程

​ 因项目需求, 配置了多个Filter对数据进行数据过滤,并且在进入controller之前需要进行一些日志处理,日活统计,数据预处理等行为,所以需要多次从ServletRequest获取请求体数据, 但是因为HttpServletRequest中流读取导致的标志位的移动, 使得数据只能读取一次,因此利用HttpServletRequestWrapper进行数据缓存。

​ 因为我controller层预处理的逻辑是相同,所以通过@ControllerAdvice 并且实现RequestBodyAdviceAdapter类方式进行了处理,在实现类中指定了需要预处理的url路径和复写了beforeBodyRead方法,但是这里踩了一个小坑: 在beforeBodyRead方法中使用inputMessage.getBody().read(body)读取的数据永远是body数组的第一个元素,原因是在实现的RequestWrapper类中创建的ServletInputStream对象未复写available()方法,导致调用PushbackInputStream类中available()方法时调用的是InputStream.available()方法,返回的结果是0, 而不是数组的实际大小,导致获取数据异常。

下面记录一下这几个类的实现

wrapper实现,用于缓存body数据

RequestWrapper
public class RequestWrapper extends HttpServletRequestWrapper {

    /**
     * 参数字节数组
     */
    private byte[] body;

    /**
     * Http请求对象
     */
    private HttpServletRequest request;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }

    /**
     * 获取输入流, 这里先将数据读取出来保存到body中
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (null == this.body) {
            ByteArrayOutputStream outs = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), outs);
            this.body = outs.toByteArray();
        }

        final ByteArrayInputStream in = new ByteArrayInputStream(body);
        return new MyServletInputStream(in);
    }

    public static class MyServletInputStream extends ServletInputStream {

        private ByteArrayInputStream stream;

        public MyServletInputStream(ByteArrayInputStream stream) {
            this.stream = stream;
        }

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

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

        @Override
        public void setReadListener(ReadListener readListener) {}
        
        @Override
        public int read() throws IOException {return stream.read(); }

        @Override
        public int available() throws IOException {
            return stream.available();        
        }
    }

    public byte[] getRequestBody() {
        return body;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

}

Filter 实现, 一般wrapper都配合wrapper实现,这里相对重要,因为需要将wrapper数据放入到filter链路中

ChannelFilter
public class ChannelFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(servletRequest instanceof HttpServletRequest) {
            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        if(requestWrapper == null) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            //使用复写后的wrapper
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

RequestBodyAdviceAdapter 实现类,在这个地方进行数据预处理

ExtractRequest
@Slf4j
@ControllerAdvice
public class ExtractRequest extends RequestBodyAdviceAdapter {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
		// 这里返回true 那么执行后续beforeBodyRead 方法,返回false不执行
        // 这里我是依照需求, 根据了url 进行了正则判断
        String[] value = methodParameter.getContainingClass().getAnnotation(RequestMapping.class).value();
        return Arrays.stream(value).anyMatch(c -> ReUtil.isMatch("/contract/.*", c));
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
		// 获取inputStream 中的body 的字节数组
        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);
        try {
            // 实现自己的业务逻辑
			......
        } catch (Exception e) {
            log.error("解析请求参数失败:{}", e.getMessage());
        }
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}

请求路径解析

一个请求发送到tomcat,如果存在 filter那么会优先执行filter,然后才会进入servlet容器中, 我们可以看进入到servlet的前一步的org.apache.catalina.core.StandardWrapperValveinvoke方法

invoke
// StandardWrapperValve
public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    ....

    // 分配一个 servlet 实例来处理这个请求
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
    } catch (UnavailableException e) {
            ......
    }

    // 为此请求创建过滤器链
    ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // 调用filter链
    // 在这个步骤同事也调用了servlet的service
    Container container = this.container;
    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 在这调用了doFilter的方法
                        filterChain.doFilter(request.getRequest(),
                                             response.getResponse());
                    }
                    ........
        }
     }

doFilter方法在ApplicationFilterChain

doFilter
// ApplicationFilterChain
public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {
    ......
        internalDoFilter(request,response);

}

doFilter 中调用internalDoFilter 方法, 该方法中执行了filter的具体doFilter方法, 同时也执行了servletservice()方法,正式进入servlet容器

internalDoFilter
// ApplicationFilterChain
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // 这里执行filter
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
				.....
                if( Globals.IS_SECURITY_ENABLED ) {
					....
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // 在filter 执行完成后执行service()
        try {

            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
				.....
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
			.......
    }
DispatcherServlet

下面我们来着重看一个类DispatcherServlet , 这个类是不是看着很眼熟, 没错, 就是SpringMVC 的核心前端控制器,我们看一下他的继承关系图

上面说到在处理完filter后进入到servlet容器, 因为我们配置了DispatcherServlet作为控制器, 首先请求会进入到HttpServlet.service() 方法,然后根据请求方式(这里根据POST方法进行说明), 进入到DispatcherServlet的父类FrameworkServlet.doPOST(), 最后调用了DispatcherSerlvet.doService()对于数据进行处理

doService中进行了一些web上下文的配置,属性的缓存等request的全局属性设置, 同时进行了请求的分发doDispatcher

doDispatch
// DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
                // 判断是否有文件上传
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 从HandlerMapping获取处理器对象,获取对应的HandlerExecutionChain
                // HandlerExecutionChain  由处理器对象和拦截器组成,拦截器相关执行都在这个类中执行
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 获取HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// 如果存在拦截器, 那么会先执行拦截器的preHandler 方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 这里是执行handler 方法,比如接收到servlet,Controller 就执行不同的Adapter
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// 拦截器的postHandler
				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

handler是调用的AbstractHandlerMethodAdapter.handler(), 然后又调用了RequestMappingHandlerAdapter.handleInternal(),

RequestMappingHandlerAdapter类在SpringMVC中相当重要的一个类,他内部含有大量的web基础组件来协助完成一阵个请求处理,这个类也比较庞大, 后续再新一份新的博客进行介绍,最后在RequestMappingHandlerAdapter类的invokeHandlerMethod方法中会生成一个ServletInvocableHandlerMethod对象对请求进行处理。

在执行到controller具体的方法之前, 需要把请求的参数解析包装成相应的实体类, 具体由InvocableHandlerMethod.getMethodArgumentValues() 方法实现, 比如解析使用了@ResquestBody方法参数的RequestResponseBodyMethodProcessor类, 利用readWithMessageConverters方法读取和转化参数, 继续跟着源码走,发现到了AbstractMessageConverterMethodArgumentResolver中,在readWithMessageConverters对数据进行了预先处理

readWithMessageConverters
// AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		......
		EmptyBodyCheckingHttpInputMessage message = null;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
                        // 因为之前的ExtractRequest类只实现了beforeBodyRead,就只执行该方法
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			省略.....
		}
		finally {
			省略.....
	}

然后调用adviceBean对象执行相应的切面方法,根据源码走到了RequestResponseBodyAdviceChain.beforeBodyRead()如下图所示

beforeBodyRead方法中传入的是EmptyBodyCheckingHttpInputMessage实例对象,在该实例中存在两个属性bodyheaders,而body则是一个PushbackInputStream实例对象,这是一个可回退的流, 在这个实例中把之前RequestWrapper作为in参数传入进去, 在ExtractRequest.beforeBodyRead方法中对请求体获取输入流的字节数的时候就会调用in.available()方法, 这也是我之前未在MyServletInputStream中复写available() 导致踩坑的原因。

posted @   苜蓿椒盐  阅读(1561)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示