SpringMVC从http流到Controller接口参数传递过程

一,前言

谈起springMVC框架接口请求过程大部分人可能会这样回答:负责将请求分发给对应的handler,然后handler会去调用实际的接口。核心功能是这样的,但是这样的回答未免有些草率。面试过很多人,大家彷佛约定好了的一般,给的都是这样"泛泛"的标准答案。最近开发遇到了这样的两个场景:

  • 1>,上游的回调接口要求接受类型为application/x-www-form-urlencode,请求方式post,接受消息为xml文本。
  • 2>,对接系统动态生成文件(文件实时变更,采用chunk编码),导致业务系统无法预览文件(浏览器会直接下载),采用中转接口对文件流进行转发。

针对上述需求,如何开发rest风格的接口解决呢?

二、request的生命周期

我们知道,当一个请求到达后端web应用(mvc架构的应用)监听的端口, 率先被拦截器拦截到,然后转交到对应的接口。我们知道底层的数据必定是数据流形式的,那么他是怎么把流转成接口需要的参数,从而发起调用的呢?此时我们便需要去研究DispathServlet的处理逻辑了。

2.1 DispatchServlet具备的职能

  • handler 容器
  • handler 前、后置处理器
  • 请求转发(交由HandlerApdater.handler()执行)
  • 响应结果转发

具体入口代码如下(DipatchServlet.doDispatch):

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);

// 找到与请求匹配的handler

mappedHandler = getHandler(processedRequest);

if (mappedHandler == null) {

noHandlerFound(processedRequest, response);

return;

}

// 找到与请求匹配的HandlerAdpater

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// ... 省略部分代码

// handler 前置处理器

if (!mappedHandler.applyPreHandle(processedRequest, response)) {

return;

}

// handler 调用: 会实际调用到我们的controller接口

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {

return;

}

applyDefaultViewName(processedRequest, mv);

// handler 后置处理

mappedHandler.applyPostHandle(processedRequest, response, mv);

}

catch (Exception ex) {

dispatchException = ex;

}

catch (Throwable err) {

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 {

// 省略部分代码

}

}

这个接口就是我们寻常所说的handler的转发逻辑。但是我们也知道了实际上去调用我们controller接口的是HandlerAdapter

2.2 HandlerAdapter具备的职能

从上述我们知道了请求的转发过程,现在我们要弄清楚handler怎么调用到我们的controller接口的(以RequestMappingHandlerAdapter为例)。

  • argumentResolvers 参数解析器,提供了supportsParameter()、resolveArgument()两个方法来告诉容器是否能解析该参数以及怎么解析
  • returnValueHandlers 返回值解析器,
  • modelAndViewResolvers 模型视图解析器
  • messageConverters 消息转换器,

跟踪源码发现(RequestMappingHandlerAdapter.invokeHandlerMethod()),他调用Controller接口发生再ServletInvocableHandlerMethod.invokeAndHandle()方法。看一下主体逻辑:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,

Object... providedArgs) throws Exception {

// 调用controller接口

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

// ... 省略部分代码

try {

// 处理返回结果

this.returnValueHandlers.handleReturnValue(

returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

}

catch (Exception ex) {

if (logger.isTraceEnabled()) {

logger.trace(formatErrorForReturnValue(returnValue), ex);

}

throw ex;

}

}

调用controller接口的方法跟踪源码会发现,主要是通过request寻找到正确的参数解析器,然后去解析参数,这里我们以@RequestBody标注的参数为例,看其是如何解析的:

(RequestResponseBodyMethodProcessor.readWithMessageConverters())

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,

Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

MediaType contentType;

boolean noContentType = false;

//... 省略部分代码

EmptyBodyCheckingHttpInputMessage message;

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()) {

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) {

throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);

}

// ... 省略部分代码

return body;

}

可以看到其实就是简单的找到适配的MessageConvert,调用其read方法即可。把参数解析出来之后,发起对controller接口调用。至此从发起请求到落到controller接口的过程就是这样子的。

2.3 总结从容器接受到请求到交付到controller接口的过程。

说说SpringMVC从http流到Controller接口参数的转换过程

上图较为完整的描述了从http报文字节流到controller接口java对象的过程,返回的处理是类型的流程不在赘述。

三、总结

有章节二知道了生命周期,我们知道严格意义上,对于问题一,我们只需要定义一个HandlerMethodArgumentResolver去专门解析类似参数(实际上我们用@RequestBody修饰的参数,那么只需要定义一个MessageConvert即可),然后注入到容器即可。针对问题二,其实只要不要覆盖原生的MessageConverts对于文件流的输出本身SpringMVC就是支持的,但是因为我们通常注入MessageConvert是通过WebMvcConfigurerAdapter实现会导致默认的转换器丢失需要特别注意。

以上是 SpringMVC从http流到Controller接口参数传递过程 的全部内容, 来源链接: utcz.com/a/122183.html

回到顶部