文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data"SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。
SpringMVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现的。Spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。
SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作,如果想使用Spring的文件上传功能,需在上下文中配置MultipartResolver。
【1】CommonsMultipartResolver配置
① CommonsMultipartResolver
Apache Commons FileUpload 1.2或更高版本的基于Servlet的 MultipartResolver实现。提供了继承于CommonsFileUploadSupport的maxUploadSize、maxInMemorySize以及defaultEncoding属性。有关默认值和接受值的详细信息,请参阅相应的ServletFileUpload/DiskFileItemFactory属性(“sizeMax”、“sizeThreshold”、“headerEncoding”)。
其会将临时文件保存到servlet容器的临时目录。需要通过应用程序上下文或通过接受ServletContext(用于独立使用)的构造函数初始化实例。
实现类如下所示,继承了CommonsFileUploadSupport
并实现了MultipartResolver
和ServletContextAware
接口。
② 主要方法
① 有参构造方法
获取servletContext实例进行CommonsMultipartResolver实例化
public CommonsMultipartResolver(ServletContext servletContext) { this(); setServletContext(servletContext); }
② 获取ServletFileUpload
@Override protected FileUpload newFileUpload(FileItemFactory fileItemFactory) { return new ServletFileUpload(fileItemFactory); }
③ 设置ServletContext 引用
@Override public void setServletContext(ServletContext servletContext) { if (!isUploadTempDirSpecified()) { getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext)); } }
④ 检测是否Multipart请求
@Override public boolean isMultipart(HttpServletRequest request) { return ServletFileUpload.isMultipartContent(request); }
⑤ 核心方法-将请求包装为MultipartHttpServletRequest
@Override public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }
⑥ 核心方法-解析请求
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 (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadBase.FileSizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex); } catch (FileUploadException ex) { throw new MultipartException("Failed to parse multipart servlet request", ex); } }
⑦ 清理fileItems内容
public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { try { cleanupFileItems(request.getMultiFileMap()); } catch (Throwable ex) { logger.warn("Failed to perform multipart cleanup for servlet request", ex); } } }
底层是对 org.apache.commons.fileupload
下面的几个类做了封装
③ 加入SpringMVC依赖的Java原生的jar
如下图所示,使用Java原生进行文件上传需要的jar包
可以使用maven依赖:
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
④ 配置CommonsMultipartResolver
spring的xml文件配置如下
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--需与jsp页面编码保持一致--> <property name="defaultEncoding" value="UTF-8"></property> <!--限制上传大小--> <property name="maxUploadSize" value="102400000"></property> </bean>
其他属性如下图所示:
属性解释如下:
resolveLazily:延迟解析,默认为false--立即解析multipart request; defaultEncoding:解析请求的默认字符编码 ; 默认值为"ISO-8859-1"。通常设置为"UTF-8"; maxUploadSize:文件上传最大值; 默认值为 -1(表示没有限制); maxUploadSizePerFile:每个文件上传最大值;默认值为 -1(表示没有限制); maxInMemorySize:存储在内存的最大值;默认值为10240B(10KB); uploadTempDir:上传文件的临时目录;默认值为WEB应用程序的临时目录; servletContext:the servletContext to use;
【2】测试代码
① form表单
<form action="testFileUpload" method="POST" enctype="multipart/form-data"> File: <input type="file" name="file"/> Desc: <input type="text" name="desc"/> <input type="submit" value="Submit"/> </form>
② 后台代码
@RequestMapping("/testFileUpload") public String testFileUpload(@RequestParam("desc") String desc, @RequestParam("file") MultipartFile file) throws IOException{ if (!file.isEmpty()) { System.out.println("desc: " + desc); System.out.println("OriginalFilename(原始文件名字): " + file.getOriginalFilename()); System.out.println("InputStream(获取的文件输入流): " + file.getInputStream()); System.out.println("文件大小为(单位为字节Byte): " + file.getSize()); System.out.println("文件内容类型为: " + file.getContentType()); file.transferTo(new File("D:\\temDirectory\\"+file.getOriginalFilename())); } return "success"; }
如上述代码所示,可以拿到文件名与输入流以及文件大小。最后一个方法file.transferTo
很有意思,可以直接保存到目标路径下的文件:
void org.springframework.web.multipart.MultipartFile.transferTo(File dest) throws IOException, IllegalStateException
- 将接收到的文件传输到给定的目标文件。
- 这可能会移动文件系统中的文件,复制文件系统中的文件,或将内存保留的内容保存到目标文件。
- 如果目标文件已存在,将首先删除它。
- 如果文件已在文件系统中移动,则无法再次调用此操作。
- 因此,只需调用此方法一次,即可使用任何存储机制。
Chrome F12追踪网络展示如下:
③ 上传多个文件
表单内容:多个文件域(上传单文件时注释掉)
<form action="face/receiveImg" method="POST" enctype="multipart/form-data"> File: <input type="file" name="file"/> Desc: <input type="text" name="desc"/> <!-- File2: <input type="file" name="file"/> --> <!-- Desc2: <input type="text" name="desc2"/> --> <input type="submit" value="Submit"/> </form>
也可以使用multiple 属性(上传单文件时去掉该属性)
<form action="face/receiveImg" method="POST" enctype="multipart/form-data"> File: <input type="file" name="file" multiple="multiple"/> Desc: <input type="text" name="desc"/> <input type="submit" value="Submit"/> </form>
上传单文件时,后台使用MultipartFile file,MultipartFile[] file,@RequestParam("file")MultipartFile file,@RequestParam MultipartFile file 均可以正常接收(这里name默认为file哦)。
上传多文件时,后台只有使用 @RequestParam("file") MultipartFile[] file才可以正常接收。
【3】使用FormData上传多个文件
通过FormData对象可以组装一组用 XMLHttpRequest发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。
如果你把表单的编码类型设置为multipart/form-data ,则通过FormData传输的数据格式和表单通过submit() 方法传输的数据格式相同
即,此时使用FormData对象传送的数据不会发送格式改变
① 表单示例
这里使用两个文本域来上传两个Excel表格:
<form id="myform" name="myform" action="<%=basePath%>data/saveDataImport.do" class="layui-form" method="post" enctype="multipart/form-data" > <div class="layui-form-item"> <label class="layui-form-label">请选择POS表:</label> <div class="layui-input-block"> <input id="pos" type="file" name="pos" > </div> </div> <div class="layui-form-item" > <label class="layui-form-label">请选择商品表:</label> <div class="layui-input-block"> <input id="goods" type="file" name="goods" > </div> </div> <div class="layui-form-item" style="margin-top: 40px;"> <div class="layui-input-block"> <button id = "upload" class="layui-btn" type="button">确定</button> <button name="cancel" type="button" class="layui-btn" onclick="f_cancel();">取消</button> </div> </div> </form>
② 构建FormData对象
下面两种正确方式:
var posFormData = new FormData($("#myform")[0]); or var posFormData = new FormData($("form")[0]);
参数对比如下图:
FormData对象
③ ajax提交
$(function(){ $("#upload").click(function(){ var posFormData = new FormData($("#myform")[0]); $.ajax({ url: url, type: 'POST', data: posFormData, contentType: false, //禁止设置请求类型 processData: false, //禁止jquery对DAta数据的处理,默认会处理 beforeSend: function(){ //返回的参数item,即为当前的input DOM对象 index = layer.load(1,{shade: [0.3,'grey']}); }, success: function(data) { if (typeof data != "object") { jsonReturn = eval("("+data+")"); }else{ jsonReturn=data; } //关闭遮罩层 layer.close(index); if(jsonReturn.code == "true"){ layer.msg(jsonReturn.message,{icon:1,time: 1000},function(){ f_cancel(); top.f_getframe("goods_index_do").f_goods_sale_query(); }); }else{ layer.msg(jsonReturn.message,{icon: 7,time: 2000}); } } }); }); });
上传参数如下图:
使用submit方式上传文件参数如下图:
对比发现,二者数据格式完全一致!
④ 后台接收
@RequestMapping(value="saveDataImport",produces="application/json;charset=utf-8" ) @ResponseBody public String saveDataImport(@RequestParam(value="pos",required=false)MultipartFile posFile,@RequestParam(value="goods",required=false)MultipartFile goodsFile){ //... }