一,前言

谈起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):

java 实现一个带参数接口 java接口作为参数_http参数自动转换java接口参数设置

java 实现一个带参数接口 java接口作为参数_文件流_02

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

2.2 HandlerAdapter具备的职能

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

argumentResolvers 参数解析器,提供了supportsParameter()、resolveArgument()两个方法来告诉容器是否能解析该参数以及怎么解析returnValueHandlers 返回值解析器,modelAndViewResolvers 模型视图解析器messageConverters 消息转换器,跟踪源码发现(RequestMappingHandlerAdapter.invokeHandlerMethod()),他调用Controller接口发生再ServletInvocableHandlerMethod.invokeAndHandle()方法。看一下主体逻辑:

java 实现一个带参数接口 java接口作为参数_文件流_03

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

java 实现一个带参数接口 java接口作为参数_http参数自动转换java接口参数设置_04

java 实现一个带参数接口 java接口作为参数_http参数自动转换java接口参数设置_05

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

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

java 实现一个带参数接口 java接口作为参数_解析器_06

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

三、总结

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