一,前言

谈起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接口的过程。

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

三、总结

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

说说SpringMVC从http流到Controller接口参数的转换过程的更多相关文章

  1. SpringMVC源码阅读:Controller中参数解析

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...

  2. SpringMVC(4.2):Controller接口控制器详解(2)

    原文出处: 张开涛 4.5.ServletForwardingController 将接收到的请求转发到一个命名的servlet,具体示例如下: package cn.javass.chapter4. ...

  3. SpringMVC(4.1):Controller接口控制器详解(1)

    原文出处: 张开涛 4.1.Controller简介 Controller控制器,是MVC中的部分C,为什么是部分呢?因为此处的控制器主要负责功能处理部分: 1.收集.验证请求参数并绑定到命令对象: ...

  4. springMVC 开涛 Controller接口控制器

    通过注解实现控制器类,所以不用看Controller接口了.把之前的笔记保存下. 笔记(图片):http://pan.baidu.com/s/1mgMNDna 第三章看不太懂,3.2 3.3.只了解到 ...

  5. golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL

        先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口.但 ...

  6. 基于Controller接口的控制器及简单应用

    DispatcherServlet在Spring当中充当一个前端控制器的角色,它的核心功能是分发请求.请求会被分发给对应处理的Java类,Spring MVC中称为Handle.在Spring 2.5 ...

  7. SpringMVC之Controller和参数绑定

    在上一篇Spring+SpringMVC+Mybatis整合中说到了SSM的整合,并且在其中添加了一个简单的查询功能,目的只是将整个整合的流程进行一个梳理,下面在上一篇中工程的基础上再说一些关于Spr ...

  8. 如何为 SpringMVC 编写单元测试:普通 Controller 测试(转)

    前一篇文章我们已经知道如何配置使用了 SpringMVC 测试框架的单元测试. 现在我们就该亲身实践下如何为普通 Controller 编写单元测试了. 接下来一个很明显的问题就是: 什么是普通 Co ...

  9. 基于实现Controller接口的简单Spring工程

    这个Spring工程的特点是:实现了Controller接口(这样就可以在url中传参数?,待调查) 一下为代码,可运行. 1,web.xml <servlet> <servlet- ...

随机推荐

  1. K8S(05)核心插件-ingress(服务暴露)控制器-traefik

    K8S核心插件-ingress(服务暴露)控制器-traefik 1 K8S两种服务暴露方法 前面通过coredns在k8s集群内部做了serviceNAME和serviceIP之间的自动映射,使得不 ...

  2. zookeeper 的监控指标(一)

    一 应用场景描述 在目前公司的业务中,没有太多使用ZooKeeper作为协同服务的场景.但是我们将使用Codis作为Redis的集群部署方案,Codis依赖ZooKeeper来存储配置信息.所以做好Z ...

  3. 使用DTK创建模糊背景窗口并自定义阴影效果

    DTK是deepin开发的基于Qt的开发套件,提供了大量的具有独特风格的美化控件,也提供了很多非常方便的API,下边我们用DTK实现一个模糊窗口,并设置其阴影效果. 使用场景 一切需要模糊窗口作为美化 ...

  4. 关于Matlab GUI的一些经验总结

    注:此文来自转载,侵删去年做了一些关于Matlab GUI的程序,现在又要做相关的东西,回想起来,当时很多经验没有记录下来,现在回顾起来始终觉得不爽,所以从现在开始,一定要勤写记录. 从简单的例子说起 ...

  5. CSS ::marker All In One

    CSS ::marker All In One CSS pseudo element / CSS 伪元素 /* user agent stylesheet */ ::marker { unicode- ...

  6. The Weekly Web Dev Challenge: Emoji Ratings

    The Weekly Web Dev Challenge: Emoji Ratings /* DESCRIPTION: You job is to enable users to give a rat ...

  7. website SEO all in one

    website SEO all in one SEO 指标量化 https://www.similarweb.com/zh/top-websites/ demo https://www.similar ...

  8. Google 灭霸 彩蛋

    Google 灭霸 彩蛋 在Google中搜索"灭霸",然后在右侧点击的"无限手套",页面的一些搜索项就会随机性像沙子一样的消失(后面统称沙化效果),特别的炫酷 ...

  9. 用WPF写了个处理视频,音频,图片的工具

    处理工具依赖ffmpeg,感兴趣可以看下Github上的源码 下载地址也在Github上

  10. 百度 Apollo无人车平台增加传感器

    https://github.com/ApolloAuto/apollo/issues/1649 如果想加入一个新的传感器不是百度官方推荐的传感器到Apollo平台做法: First you can ...