文件上传要求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​​​接口。
SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring

② 主要方法

① 有参构造方法
获取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​​​下面的几个类做了封装,可以参考博文:文件上传 - Java原生实现


③ 加入SpringMVC依赖的Java原生的jar

如下图所示,使用Java原生进行文件上传需要的jar包
SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring mvc_02
可以使用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>

其他属性如下图所示:

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring_03

属性解释如下:

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追踪网络展示如下:

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring mvc_04

③ 上传多个文件

表单内容:多个文件域(上传单文件时注释掉)

<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]);

参数对比如下图:

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_文件上传_05

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring mvc_06


FormData对象

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring_07


③ 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});
}
}
});
});

});

上传参数如下图:

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring_08


使用submit方式上传文件参数如下图:

SpringMVC使用MultipartResolver和MultipartFile实现文件上传_spring mvc_09

对比发现,二者数据格式完全一致!

④ 后台接收

@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){

//...
}