7.2.1 配置 multipart 解析器

DispatcherServlet 并没有实现任何解析 multipart 请求数据的功能。它将该任务委托给了 Spring 中 MultipartResolver 策略接口的实现,通过这个实现类来解析 multipart 请求中的内容。从 Spring 3.1 开 始,Spring 内置了两个 MultipartResolver 的实现供我们选择:

  • CommonsMultipartResolver:使用 Jakarta Commons FileUpload 解析 multipart 请求;

  • StandardServletMultipartResolver:依赖于 Servlet 3.0 对 multipart 请求的支持(始于 Spring 3.1)。

一般来讲,在这两者之 间,StandardServletMultipartResolver 可能会是优选的方案。它使用 Servlet 所提供的功能支持,并不需要依赖任何其他的项目。如果我们需要将应用部署到 Servlet 3.0 之前的容器中,或者还没有使用 Spring 3.1 或更高版本,那么可能就需要 CommonsMultipartResolver 了。

使用 Servlet 3.0 解析 multipart 请求

兼容 Servlet 3.0 的 StandardServletMultipartResolver 没有构造器参数,也没有要设置的属性。这样,在 Spring 应用上下文中,将其声明为 bean 就会非常简单,如下所示:

@Bean
public MultipartResolver multipartResolver() throws IOException {
  return new StandardServletMultipartResolver();
}

既然这个 @Bean 方法如此简单,你可能就会怀疑我们到底该如何限制 StandardServletMultipartResolver 的工作方式呢。如果我们想要限制用户上传文件的大小,该怎么实现?如果我们想要指定文件在上传时,临时写入目录在什么位置的话,该如何实现?因为没有属性和构造器参数,StandardServletMultipartResolver 的功能看起来似乎有些受限。

其实并不是这样,我们是有办法配置 StandardServletMultipartResolver 的限制条件的。只不过不是在 Spring 中配置StandardServletMultipart-Resolver, 而是要在 Servlet 中指定 multipart 的配置。至少,我们必须要指定在文件上传的过程中,所写入的临时文件路径。如果不设定这个最基本配置的话,StandardServletMultipartResolver 就无法正常工作。具体来讲,我们必须要在 web.xml 或 Servlet 初始化类中,将 multipart 的具体细节作为 DispatcherServlet 配置的一部分。

如果我们采用 Servlet 初始化类的方式来配置 DispatcherServlet 的话,这个初始化类应该已经实现了 WebApplicationInitializer,那我们可以在 Servlet registration 上调用 setMultipartConfig() 方法,传入一个 MultipartConfig-Element 实例。如下是最基本的 DispatcherServlet multipart 配置,它将临时路径设置为 /tmp/spittr/uploads

DispatcherServlet ds = new DispatchServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));

如果我们配置 DispatcherServlet 的 Servlet 初始化类继承了 Abstract-AnnotationConfigDispatcherServletInitializer 或 AbstractDispatcher-ServletInitializer 的话,那么我们不会直接创建 DispatcherServlet 实例并将其注册到 Servlet 上下文中。这样的话,将不会有对 Dynamic Servlet registration 的引用供我们使用了。但是,我们可以通过重载customize-Registration() 方法(它会得到一个 Dynamic 作为参数)来配置 multipart 的具体细节:

@Override
protected void customizeRegistration(Dynamic registration) {
  registration.setMultipartConfig(
    new MultipartConfigElement("/tmp/spittr/uploads");
  );
}

到目前为止,我们所使用是只有一个参数的 MultipartConfigElement 构造器,这个参数指定的是文件系统中的一个绝对目录,上传文件将会临时写入该目录中。但是,我们还可以通过其他的构造器来限制上传文件的大小。除了临时路径的位 置,其他的构造器所能接受的参数如下:

  • 上传文件的最大容量(以字节为单位)。默认是没有限制的。

  • 整个 multipart 请求的最大容量(以字节为单位),不会关心有多少个 part 以及每个 part 的大小。默认是没有限制的。

  • 在上传的过程中,如果文件大小达到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为 0,也就是所有上传的文件都会写入到磁盘上。

例如,假设我们想限制文件的大小不超过 2MB,整个请求不超过 4MB,而且所有的文件都要写到磁盘中。下面的代码使用 MultipartConfigElement 设置了这些临界值:

@Override
protected void customizeRegistration(Dynamic registration) {
  registration.setMultipartConfig(
    new MultipartConfigElement("/tmp/spittr/uploads",
      2097152, 4194304, 0);
  );
}

如果我们使用更为传统的 web.xml 来配置 MultipartConfigElement 的话,那么可以使用 <servlet> 中的 <multipart-config> 元素,如下所示:

<servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatchServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
  <multipart-config>
    <location>/tmp/spittr/upload</location>
    <max-file-size>2097152</max-file-size>
    <max-request-size>4194304</max-request-size>
  </multipart-config>
</servlet>

<multipart-config> 的默认值与 MultipartConfigElement 相同。与 MultipartConfigElement 一样,必须要配置的是 <location>。

配置 Jakarta Commons FileUpload multipart 解析器

通常来讲,StandardServletMultipartResolver 会是最佳的选择,但是如果我们需要将应用部署到非 Servlet 3.0 的容器中,那么就得需要替代的方案。如果喜欢的话,我们可以编写自己的 MultipartResolver 实现。不过,除非想要在处理 multipart 请求的时候执行特定的逻辑,否则的话,没有必要这样做。Spring 内置了 CommonsMultipartResolver,可以作为 StandardServletMultipartResolver 的替代方案。

将 CommonsMultipartResolver 声明为 Spring bean 的最简单方式如下:

@Bean
public MultipartResolver multipartResolver() {
  return new MultipartResolver();
}

与 StandardServletMultipartResolver 有所不同,CommonsMultipart-Resolver 不会强制要求设置临时文件路径。默认情况下,这个路径就是 Servlet 容器的临时目录。不过,通过设置 uploadTempDir 属性,我们可以将其指定为一个不同的位置:

@Bean
public MultipartResolver multipartResolver() throws IOException {
  CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
  multipartResolver.setUploadTempDir(
    new FileSystemResource("/tmp/spittr/uploads");
  );
  return multipartResolver;
}

实际上,我们可以按照相同的方式指定其他的 multipart 上传细节,也就是设置 CommonsMultipartResolver 的属性。例如,如下的配置就等价于我们在前文通过 MultipartConfigElement 所配置的 StandardServlet-MultipartResolver:

@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;
}

在这里,我们将最大的文件容量设置为 2MB,最大的内存大小设置为 0 字节。这两个属性直接对应于 MultipartConfigElement 的第二个和第四个构造器参数,表明不能上传超过 2MB 的文件,并且不管文件的大小如何,所有的文件都会写到磁盘中。但是与 MultipartConfigElement 有所不同,我们无法设定 multipart 请求整体的最大容量。

Last updated