mint mvc上传文件的实现封装在mint.mvc.core.upload包内,见下图:
mint.mvc.core.upload包的结构
一张大图弥补我拙劣的语言表达能力:
下面来细说upload包中6个类(也就是六个小图···)。
MultipartConfig
MultipartConfig注解是用来配置文件上传的处理信息的,用法如下:
@MultipartConfig(attributeName = "files", limitSize = 1024*1024*1024, tempFilePath = "D:/upload")
@Mapping(urls={"/index", "/index/{id}"}, method="post")
public String index(Integer id, String name, String username, MultipartParameter[] files, HttpServletRequest request){
return "index";
}
暂时有4个配置项:
- tempFilePath:临时文件存放目录
- attributeName:当文件上传完毕后,可以通过request.getAttribute("[attributeName]")方法获得上传文件(和普通域)的数组
- maxRequestSize:请求Content-length的最大长度
- limitSize:每个文件或普通域的最大长度
MultipartHttpServletRequest
MultipartHttpServletRequest继承javax.servlet.http.HttpServletRequestWrapper,是用来封装多媒体表请求。
MultipartHttpServletRequest重写了HttpServletRequestWrapper 的 getPart、getParts、getParameter、getParameterMap方法。并且添加下面的几个方法:
- public MultipartParameter getMultipartParameter(String name):获取多媒体表单的参数,包括多媒体(文件)和普通参数
- public MultipartParameter[] getMultipartParameters():获取多媒体表单的所有参数,包括多媒体(文件)和普通参数
- public Set<String> getParameterNamesSet():返回所有参数名
MultipartParameter
MultipartParameter用来封装多媒体表单的参数,每一个文件域或者文本域封装为一个MultipartParameter对象。该接口继承javax.servlet.http.Part,并且额外声明4个方法:
- public boolean isFile():判断当前参数是否为文件
- public String getParameterValue():获取文本域参数的值
- public File getTempFile():获取上传的临时文件
- public String getFilename():获取上传文件在上传时的文件名
DefaultMultipartParameter
DefaultMultipartParameter是MultipartParameter的一个实现,详见MultipartParameter和javax.servlet.http.Part
FileUpload
FileUpload是文件上传的工具类,介绍它的两个方法:
- public void upload(String tempFilePath, String attributeName, long limitSize, AsyncListener listener):上传文件。tempFilePath、attributeName、limitSize和MultipartConfig的配置项对应,listener声明了异步上传完成之后的回调方法。
- public static boolean upload(String tempFilePath, String attributeName, long limitSize, AsyncContext acontext, Object lock):前三个参数对应MultipartConfig的配置项。acontext是servlet3的异步处理上下文,lock如果想同步上传文件,可以在上传文件开始时阻塞当前线程,当文件上传完毕后,上传线程会唤醒被阻塞的线程,以继续运行
UploadExecutor
UploadExecutor是上传文件的真正执行者。负责多媒体表单的解析,把解析得到的参数封装到一个MultipartParamter数组里,最后调用request的SetAttribute方法,以指定attributeName把MultipartParamter数组保存request内。
文件上传的过程
上传的宏观过程
如图所示,当接到头部声明有形如"Content-Type:multipart/form-data; boundary=----WebKitFormBound"的请求时,mint-mvc就会开启线程处理多媒体参数,并阻塞当前前端控制器的线程,待多媒体参数解析完毕后,唤醒前端控制器的线程。
以往文件上传的情况是,servlet应用服务器会包办文件上传这个过程,直到把请求数据接受完毕才调用指定的servlet,这样的做法有不少缺点:
- 一个根本不是设计来接受文件的servlet,也不会拒绝一个多媒体post请求,直到把请求数据接受完毕才调用指定的servlet。这样做是很浪费资源的,甚至是很危险的
- 在servlet3.0之前,一个处理上传头像的servlet,也可能自作多情地去接受一个几GB的windows 安装镜像,并且接受完毕才通知你,实在无法忍受
mint mvc如此实现文件上传,是试图用servlet3的异步特性,迫使servelt 服务器把文件上传的过程完全交给mint mvc或者使用者来处理,这样做希望达到如下效果:
- servlet 应用服务器只负责接受多媒体请求的头部,然后就把剩下的工作交给servlet处理。servlet根据头部描述决定请求体是否要继续接受,比如说content-length是否超出限制,甚至还可以要求客户端把表单中包含的文件的简要信息封装在头部,以便后端进行更加细粒度的判断
- 不打算处理多媒体参数的servlet可以提前拒绝 post过来的多媒体请求
实际上有点遗憾,servlet3并没有规定在异步功能开启时,文件接收由谁来完成——servlet服务器还是servlet。所以不同servlet服务器有不同的实现。测试中发现tomcat7在开启异步功能时,会把文件上传的工作交给servlet处理,而jetty则不会,至于其他服务器就不知道了
解析文件的过程
解析文件和参数的过程这里只做简单介绍。
先看看一个包含多个文件域和文本域的http请求头的结构:
再来看对应请求体的结构(请忽略行号):
请求头中的 boundary指的是请求体中的分隔符,但是请求体中的分隔符前面还要多“--”两个字符。请求体最后一个分隔符末尾还额外添加“--”两个字符。
请求体中分隔符和参数描述信息后面还有回车符(\r)和换行符(\n),最后一个分隔符后面有一个换行符(\n)。每个参数的描述头和参数值之间用回车换行符隔开。这些字符作为字符串输出时,看到的效果就是换行。提交数据实际上的格式是这样的:
综上所述,假如HTTP头指定的分隔符是“xxxx”,那么实际上请求体内的数据的格式是这样的:
--xxxx\r\n\r\n<数据>\r\n--xxxx\r\n\r\n<数据>\r\n--xxxx--\n
然后可以知道从请求体中解析参数的大致过程如下:
至于代码怎么写,在此就不说了。
项目地址:http://git.oschina.net/895925636/mint-mvc
收录地址:http://www.oschina.net/p/mint-mvc
博客地址:http://www.wemakers.net/home/blog?cate=1001
(完)