记录工作中遇到的一点小麻烦
先描述问题吧,项目中web上传通过Spring的MultipartResolver处理,后来因需求变更需要上传几G的大文件,就采用插件分片上传,后台接收文件时需要获取ServletFileUpload中的FileItems中的某些属性,所以就将MultipartResolver的配置取消掉了,结果上线后发现之前正常工作的普通http接口都出现异常,无法获取请求中的参数。通过postman调试发现是content-type为multipart/form-data的请求参数无法获取
显然这里有两个问题,我们分开来说。首先为何Controller无法获取multipart/form-data编码形式的参数,而x-www-form-urlencoded编码却可以呢?通过查阅资料可知,x-www-form-urlencoded顾名思义,会对请求消息进行URLENCODE,空格转换为 "+" 加号,特殊符号转换为 ASCII HEX 值,而multipart/form-data不对字符进行编码,使用二进制数据传输,一般用于上传文件,非文本的数据传输。那为何之前项目的接口方采用multipart/form-data的编码方式能正常获取参数呢?自然是MultipartResolver的功劳了,通过Spring源码可知,在实现类中CommonsMultipartResolver中:
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
// 获取请求的编码类型
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
} catch (...) {}
}
在 parseRequest() 方法中,首先调用了 prepareFileUpload() 方法来根据编码类型确定一个 FileUpload 实例,然后利用这个 FileUpload 实例解析请求数据后得到文件信息,最后将文件信息解析成 CommonsMultipartFile (实现了 MultipartFile 接口) 并包装在 MultipartParsingResult 对象中。所以Spring实际在这里获取到了request中的参数,然后封装好供Controller获取了。同时在这里我们再回想下第二个问题,为何启用MultipartResolver后无法获取FileItems了?答案也在这段代码中,正如上文所说,Spring将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller,自然而然FileItems就不存在了
最后总结一下吧:
一、接口的编码方式一定要预先定义好,中间服务层代码好改,让调用方们重新构造请求时会死人的
二、虽然使用MultipartResolver后FileItems就无法获取了,但是Spring已经将一切信息都封装在MultipartHttpServletRequest中,可以很容易的获取得到,实例代码如下:
@RequestMapping(value = "/multipartDemo", method = RequestMethod.POST)
@ResponseBody
public String multipartDemo(HttpServletRequest request, HttpServletResponse response) {
//获取multipartRequest
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
//获取表单文本
String formText = multipartRequest.getParameter("formText");
//获取上传文件
MultipartFile multiFile = multipartRequest.getFile("multiFile");
//转存本地
File localFile = new File("C:\\local.tet");
multiFile.transferTo(localFile);