一,前言

谈起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. nyoj-1236 挑战密室

    挑战密室 时间限制:1 s | 内存限制:128 M 提交 状态 排名 题目描述 R组织的特工Dr. Kong 为了寻找丢失的超体元素,不幸陷入WTO密室.Dr. Kong必须尽快找到解锁密码逃离,否 ...

  2. JAVA中高精度金额计算

    一般java代码中遇到高精度金额计算,日常使用bigDecimal类型. 在使用BigDecimal类来进行计算的时候,主要分为以下步骤: 1.用float或者double变量构建BigDecimal ...

  3. 【rocketmq学习笔记】rocketmq入门学习

    基本介绍 rocketmq是阿里巴巴团队使用java语言开发的一款基于发布订阅模型的分布式消息队列中间件,是一款低延迟,高可用,拥有海量消息堆积能力和灵活拓展性的消息队列. 特点 可以实现集群无单点故 ...

  4. UA 广告 All In One

    UA 广告 All In One UA 广告是什么 广告投放 / 市场营销 互联网营销和分析专用名词速览 http://www.chinawebanalytics.cn/digital-marketi ...

  5. TypeScript 1.7 & TypeScript 1.8

    TypeScript 1.7 & TypeScript 1.8 1 1 https://zh.wikipedia.org/wiki/TypeScript TypeScript是一种由微软开发的 ...

  6. How to get a DOM element's ::before content with JavaScript?

    How to get a DOM element's ::before content with JavaScript? https://stackoverflow.com/questions/443 ...

  7. zrender & svg

    zrender & svg window.prompt double click https://codepen.io/xgqfrms/pen/jOEGNvw // https://cdn.x ...

  8. Nodejs file path to url path

    import * as path from 'path'; import * as url from 'url'; const savePath = path.join('public', 'imag ...

  9. 华盛顿邮报专访:SPC能否再掀起币圈新浪潮?

    近日,美国知名媒体华盛顿邮报对话NGK灵石团队技术副总裁Daphne Patel女士,对话主题为"SPC能否再掀起币圈新浪潮".此次对话以问答的形式展开,将SPC的最新情况呈现在你 ...

  10. BGV等 DeFi产品暴涨背后隐藏着什么?

    比特币突破两万七千美金,在此创造了历史.在比特币一路飙升的背后,到底是谁注入了"强心针".笔者认为今年以来推动BTC长期上涨的主要动力主要包括四个:经济形势恶化.央行大量放水(主要 ...