探究MultipartResolver的运行原理
errol发表于2023-07-09 09:50:49 | 分类为 编程 | 标签为springjavaMultipartResolver文件上传

MultipartResolver是Spring框架中专门用于处理文件上传的接口。

它一共有三个方法,其中isMultipart方法用于检查当前请求是否为文件上传请求,如果isMultipart返回true,则继续使用resolveMultipart方法对请求进行解析,以便后续能获取到上传的数据,最后使用cleanupMultipart清除保存的数据。

public interface MultipartResolver {
    boolean isMultipart(HttpServletRequest request);
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
    void cleanupMultipart(MultipartHttpServletRequest request);
};

在Spring 3.1以后,Spring提供了两种MultipartResolver接口的实现:

  • CommonsMultipartResolver
  • StandardServletMultipartResolver

对于CommonsMultipartResolver来说,其本身需要依赖Apache的fileupload和commons-io两个jar包;

StandardServletMultipartResolver虽然不需要依赖其他的jar包,但要求Servlet的版本在3.0以上,也就是需要Tomcat的版本在7.0以上(一般java都会使用tomcat作为项目部署的服务器),主要是因为使用到了Servlet 3.0以后才会有的getParts方法,来获取请求中multipart/form-data类型的数据。

两种实现都有各自的特点,可根据实际的场景选择其中一种方案。

一、配置MultipartResolver

若想要在spring web项目中用MultipartResolver的文件解析功能,就必须先配置MultipartResolver,可以使用配置文件或注解的形式进行配置实例对象。

本文使用的是StandardServletMultipartResolver方案,因为是spring-webmvc自带的,用起来比较方便。

如下图为使用xml的形式将StandardServletMultipartResolver加入ioc容器。

image

图1 springmvc.xml

同时,根据文档要求,MultipartResolver必须指定上传文件和请求的大小上限,以下为在web.xml中配置了MultipartResolver的参数multipart-config,此外还配置了接管所有请求的DispatcherServlet。

image

图2 web.xml

二、MultipartResolver解析请求中的文件数据

加入ioc容器的StandardServletMultipartResolver,会在接收到请求的时候被调用,该方法会根据请求头的Content-Type字段是否为“multipart/form-data”,来判断当前是不是上传文件请求,以及解析其中的上传文件数据。

image

图3 DispatcherServlet#doDispatch方法体

image

图4 checkMutipart方法体

目前只需要知道spring调用了方法解析请求中的上传数据即可,至于解析的逻辑,有需要的读者可以自行查看

三、接收上传文件

一般情况下,上传文件是在控制器方法中接收的,与普通方法调用不同的是,spring的控制器方法支持“任凭开发者定义形参”,spring会智能自动地传入实参。

事实上,这是spring利用了java的反射机制:首先会获取控制器方法的参数列表类型,再根据参数的类型,在当前请求context中查找对应的数据,最后通过反射调用方法。

以下为spring调用控制器方法的调用链:

1、DispaterServlet#doDispatch【“#”前为类,“#”后为方法,“=>”表示在类中的代码】

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

2、AbstractHandlerMethodAdapter#handle

=> handleInternal(request, response, (HandlerMethod) handler);

3、RequestMappingHandlerAdapter#handleInternal

=> invokeHandlerMethod(request, response, handlerMethod);

==> invocableMethod.invokeAndHandle(webRequest, mavContainer);

4、ServletInvocableHandlerMethod#invokeAndHandle

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

5、InvocableHandlerMethod#invokeForRequest

经过漫长的调用,终于来到了控制器方法参数获取、调用的逻辑。

image

图5 invokeForRequestt方法体

在getMethodArgumentValues方法里,首先会获取控制器方法的参数列表,接着使用参数解析器循环判断是否支持解析当前参数类型,如果支持就使用参数解析器获取对应的数据。

image

图6 getMethodArgumentValues方法体

当前的上传文件方法的参数类型为MultipartFile,通过断点调试,得知MultipartFile类型的参数解析器为RequestParamMethodArgumentResolver。

image

图7 resolveArgument方法体1

image

图8 resolveArgument方法体2

最后在RequestParamMethodArgumentResolver#resolveMultipartArgument方法里找到上传文件数据,并在upload方法中成功获取到上传的文件。

image

图9 resolveMultipartArgument方法体

image

图10 upload方法体

通过以上的分析可知道,只要spring存在支持解析控制器方法定义的参数类型,并且该参数类型存在于当前请求context中的话,spring就可以帮助获取到数据,这就是spring支持“任凭”开发者定义形参的原因,不仅如此,spring甚至还支持拓展参数解析器,以适应更多的开发场景。

需要提醒的是,文章的代码分析是经过简化的,实际上spring对于文件解析和参数处理的逻辑要复杂得多,在spring中,类似MultipartResolver这样的组件,还有很多很多,它们共同构成了一套Web的解决方案。

spring框架的封装非常复杂,逻辑也很紧密,开发中的很多可能遇到的难题,它都着手处理了,所以spring很强大,使用起来也简单,让开发者爱不释手;但相对的,正因为封装过于复杂,也带来了难理解的问题,看不懂也是家常便饭。

四、结语

一般来说,只要查阅相关的技术文档,就能知道某个方法或函数的调用方式,这其实已经足够应付日常的开发,因此,源码对于开发者来说不是必须的,看了可能还没什么用:平时该怎么开发,看了之后还是该怎么开发。

但是,在我看来,如果时间允许的话,还是很推荐去阅读源码的,只不过,与其他人不同的是,我只推荐稍微阅读一点点,粗略了解其中的原理即可,不需要过于深入,也不用花太多的精力,因为普通的开发者最多只是使用框架,而不是开发框架。

不过,即使是粗浅的了解,也依然存在好处。

一方面,在开发过程中出现问题的时候,可以更快速定位并解决问题;另一方面,可以从中“窃取”源码的编程思想,相信哪怕只是学到其中的一点,也能够提升个人的编程水平,同时也拥有了跟别人谈论相关话题的资本。

(文本所用到的demo在这里)

以上就是关于MultipartResolver运行原理的全部内容。

返回