SpringMVC以multipart形式来上传文件。在编写控制器方法处理文件上传之前,我们必须要配置一个multipart解析器,通过它来告诉DispatcherServlet该如何读取multipart。
DispatcherServlet并没有实现任何解析multipart请求数据的功能。它将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这些实现类来解析multipart请求中的内容。从Spring3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:
a、CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求;
b、StandardServletMultipartResolver:依赖于Servet3.0对mutipart请求的支持(始于Spring3.1)。
一般来说,StandardServletMultipartResolver会是优先选择的方案。它使用Servlet所提供的功能支持,并不需要依赖任何其他的项目。
在Spring应用上下文中,声明StandardServletMultipartResolver非常简单:
@Bean
public MultipartResolver multipartResolver() throws IOException{
return new StandardServletMultipartResolver();
}
如果我们想要限制用户上传文件的大小或指定写入目录的话应该怎么设置呢?因为没有属性和构造器参数,StandardServletMultipartResolver本身没办法完成配置工作,而是需要在Servlet中指定multipart的配置。具体来讲,我们需要在web.xml或Servlet初始化类中,将multipart的具体细节作为DispatcherServlet配置的一部分。
如果我们采用Servlet初始化类,并且初始化类继承了AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializr的话,那我们可以通过重载customizeRegistration()方法(它会得到一个Dynamic作为参数)来配置multipart的具体细节:
@Override
protectd void customizeRegistration(Dynamic registration){
registration.setMultipartConfig(new multipartConfigElement("/tmp/spittr/uploads"));
}
我们还可以通过其他构造器来限制文件上产大小等如下参数:
a、上传文件的最大容量(以字节为单位)。默认是没有限制的。
b、整个multipart请求的最大容量(已字节为单位),不会关心有多少个part以及每个part的大小。默认是没有限制的。
c、在上传过程中,如果文件大小达到了一个指定最大容量(已字节为单位),将会写入到临时文件路径中。默认值为0,也就是所有上传的文件都会写入到磁盘上。
下面是一个限制文件大小为2MB、整个请求不超过4MB,而且所有文件都要写到磁盘中的示例:
@Override
protected void customizeRegistration(Dynamic registration){
registration.setMultipartConfig(new MultipartConfigElement("tem/spittr/uploads",2097152,4194304,0));
}
如果我们要使用更传统的web.xml来配置MultipartConfigElement的话,可以使用
中的
元素,如下所示:
<servlet>
<servlet-name>appServelt</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>/tmp/spittr/uploads</location>
<max-file-size>2097152</max-file-size>
<max-request-size>4194304</max-request-size>
</multipart-config>
</servlet>
通常来讲,StandardServletMultipartResolver会是最佳的选择。但是如果我们需要将项目部署到非Servlet3.0的容器中,那么就需要Jakarta Conmmons FileUpload multipart这个替代方案了。完成上面相同功能的配置如下:
@Bean public MultipartResolver multipartResolver() throws IOException{
CommonsMultipartResolver multipartResolver= new CommonsMultipartResolver();
multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
multipartResolver.setMaxUploadSize(2097152);
multipartResolver.setMaxInMemorySize(0);
return multipartResolver;
}
不过这种方式我们没办法设定multipart请求整体的最大容量。
在上面的步骤中我们已经配置好了对multipart请求的处理,那么接下来我们就可以编写控制器方法来接收上传的文件。要实现这一点,最常见的方式就是在某个控制器方法参数上添加@RequestPart注解。
假设我们允许用户在注册时上传一张图片,那么表单(视图为Thymeleaf)中需要添加的地方是:
标签将enctype设置为multipart/form-data,这会告诉浏览器以multipart数据的形式提交表单。在multipart中,每个输入域都会对应一个part。type为file的
标签用来选择文件,accept属性用来将文件类型限制为JPEG、PNG以及GIF图片。根据其那么属性,图片数据将会发送到multipart请求中的picture part中。
现在,我们需要修改控制器中相应的方法,使其能够接受上传的图片。其中一种方式是添加byte数组参数,并为其添加@RequestPart注解。如下所示:
@RequestMapping(value="/register",method=POST)
public String processRegistration(@RequestPart("picture") byte[] picture,@Valid Spitter spitter,Errors errors){
...
}
当注册表单提交时,picture属性将会给定一个byte数组,这个数组中包含了请求中对应part的数据(通过@RquestPart指定)。如果用户提交表单的时候没有选择文件,那么这个数组会是空(而不是null)。获取到图片数据后,控制器方法剩下的任务就是将文件保存到某个位置。
使用上传文件的原始byte比较简单但是功能有限(例如无法得知文件的具体类型、文件的原始文件名)。因此,Spring还提供了MultipartFile接口,它为处理multipart数据提供了内容更为丰富的对象。如下的程序清单展现了MultipartFile接口的概况:
package org.springframework.web.multipart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public interface MultipartFile{
String getName();
String getOriginalFilename();
String getContentType();
Boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
void transferTo(File dest) throws IOException;
}
可以看到,MultipartFile提供了众多的功能(例如获取上传文件byte方式,获取原始文件名、大小以及内容类型),它还提供了一个InputStream,用来将文件数据以流的方式进行读取。除此之外,该接口还提供了一个便利的transferTo()方法,它能够帮助我们将上传的文件写入到文件系统中。作为样例,我们可以在控制器方法中添加如下的几行代码,从而将上传的图片文件写入到文件系统中:
picture.transferTo(new File("/data/spittr/"+picture.getOriginalFilename())));
如果我们要将应用部署到Servlet3.0的容器中,那么会有MultipartFile的一个替代方案。SpringMVC也能接受javax.servlet.http.Part作为控制器方法的参数。如果使用Part来替换MultipartFile的话,那么processRegistration()方法的签名将会变成如下形式:
@RequestMapping(value="/register",method=POST)
public String processRegistration(@RequestPart("picture") Part picture,@Valid Spitter spitter,Errors errors){
...
}
就主体而言,Part接口与MultipartFile并没有太大的差别,下面是Part接口的定义:
package javax.servlet.http;
import java.io.*;
import java.util.*;
public interface Part{
public InputStream getInputStream() throws IOException;
public String getContentType();
public String getName();
public String getSubmittedFileName();
public long getSize();
public void write(String fileName) throws IOException;
public void delete() throws IOException;
public String getHeader(String name);
public Collection getHeaders(String name);
public Collection getHeaderNames();
}
类似的,我们可以通过下面的代码将文件写入到文件系统中:
picture.write("/data/spittr/"+picture.getSubmittedFileName());
值得一提的是,如果再编写控制器方法时,通过Part参数的形式接受文件上传,那么久没有必要配置MultipartResolver。只有使用MultipartFile的时候,我们才需要MultipartResolver。