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.StandardWrapperValve
的invoke
方法
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
方法, 同时也执行了servlet
的service()
方法,正式进入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
实例对象,在该实例中存在两个属性body
,headers
,而body
则是一个PushbackInputStream
实例对象,这是一个可回退的流, 在这个实例中把之前RequestWrapper
作为in
参数传入进去, 在ExtractRequest.beforeBodyRead
方法中对请求体获取输入流的字节数的时候就会调用in.available()
方法, 这也是我之前未在MyServletInputStream
中复写available()
导致踩坑的原因。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南