记一次文件上传问题


✅ Spring Boot 中文件上传的两种解析器比较

项目 CommonsMultipartResolver StandardServletMultipartResolver(Spring Boot 默认)
依赖库 Apache Commons FileUpload(外部依赖) Servlet 3.0+(Java 原生)
是否默认启用 ❌ 否(需手动配置 Bean) ✅ 是(Spring Boot 自动配置)
是否懒加载 ❌ 否(调用 resolveMultipart() 时立即解析) ✅ 是(延迟到调用 getParts() 时解析)
是否自动封装为 MultipartHttpServletRequest ❌ 否(你需要手动传给下游) ✅ 是(Spring 自动封装并传递)
是否会提前消费请求流 ✅ 是(流会被读取) ❌ 否(不消费流,基于 Servlet 3 API)
是否调用 setMultipartFiles()(注册上传文件) ✅ 是(在 resolveMultipart() 中) ✅ 是(内部自动处理)

✅ 常见错误使用示例(你的情况)

1
2
3
4
5
6
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
if (resolver.isMultipart(request)) {
MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
// ❌ 错误:你没有传 multipartRequest
filterChain.doFilter(request, response); // 原始 request,被提前消费,Controller 拿不到文件
}

✅ 正确使用 CommonsMultipartResolver 的方式:

1
2
3
4
5
6
7
8
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
if (resolver.isMultipart(request)) {
MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
// ✅ 正确:将处理后的 request 向下传递
filterChain.doFilter(multipartRequest, response);
} else {
filterChain.doFilter(request, response);
}

✅ 如果你使用的是 StandardServletMultipartResolver:

1
2
3
4
5
6
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
if (resolver.isMultipart(request)) {
// 不需要提前包装,只判断是否 multipart 即可
// 不调用 .resolveMultipart() 也没关系
}
filterChain.doFilter(request, response); // 原始 request,Spring 会自动处理

为什么StandardServletMultipartResolver不会影响后面的处理呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void parseRequest(HttpServletRequest request) {
try {
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<>(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
ContentDisposition disposition = ContentDisposition.parse(headerValue);
String filename = disposition.getFilename();
if (filename != null) {
if (filename.startsWith("=?") && filename.endsWith("?=")) {
filename = MimeDelegate.decode(filename);
}
files.add(part.getName(), new StandardMultipartFile(part, filename));
}
else {
this.multipartParameterNames.add(part.getName());
}
}
//重点在这里
setMultipartFiles(files);
}
catch (Throwable ex) {
handleParseFailure(ex);
}
}
  • setMultipartFiles(files),在 Spring 的 MultipartHttpServletRequest 对象中注册上传的文件信息,以供后续使用(如 Controller 中的 @RequestParam MultipartFile file 获取文件)。

🧠 重点记忆:

  • StandardServletMultipartResolver 更推荐,Spring Boot 默认支持,懒加载不消费流。
  • CommonsMultipartResolver 不推荐使用于 Spring Boot,若使用必须手动传递解析后的 MultipartRequest,否则上传失败。
  • ⚠️ 一旦提前用 .getInputStream().getBytes() 消费流,Controller 中将无法再获取文件。