# 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 就会非常简单，如下所示：

```java
@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`：

```java
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 的具体细节：

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

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

* 上传文件的最大容量（以字节为单位）。默认是没有限制的。
* 整个 multipart 请求的最大容量（以字节为单位），不会关心有多少个 part 以及每个 part 的大小。默认是没有限制的。
* 在上传的过程中，如果文件大小达到了一个指定最大容量（以字节为单位），将会写入到临时文件路径中。默认值为 0，也就是所有上传的文件都会写入到磁盘上。

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

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

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

```markup
<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 的最简单方式如下：

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

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

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

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

```java
@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 请求整体的最大容量。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/di-7-zhang-spring-mvc-de-gao-ji-ji-shu/untitled-4/untitled-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
