Apache Commons FileUpload 快速入门与集成示例
Apache Commons FileUpload 2.0.0-M5 快速入门:Maven 与离线 jar 依赖引入、核心 API、core 直接解析与服务端集成两个可独立运行的完整示例及实测回显。
Apache Commons FileUpload 快速入门与集成示例
目标:以最小代价跑通 Apache Commons FileUpload 2.0.0-M5 的完整链路——依赖引入、核心 API、无 Web 服务器的 multipart 解析示例与 Servlet 集成示例,全部代码可独立编译运行,已在 Ubuntu 22.04.5 服务器实测通过。
一、环境信息
本节列出文档实测的服务器基础信息。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- 组件版本:Apache Commons FileUpload 2.0.0-M5(core + jakarta-servlet5)
- 依赖:commons-io 2.21.0、jakarta.servlet-api 5.0.0、Jetty 11.0.26
- 实验目录:
/opt/fu-lab(bin 包)、/opt/fu-lab/jetty-demo(Maven 工程)
二、前置条件
本节说明运行示例前需要准备的环境(完整安装步骤见《Ubuntu 22.04 安装 Apache Commons FileUpload 指南》篇)。
- JDK 17 与 Maven 可用:
java -version输出 17.x - FileUpload 2.0.0-M5 bin 包已解压至
/opt/fu-lab,或 Maven 工程已建(二者其一即可)
验证:
java -version 2>&1 | head -1
ls /opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar
实测回显:
openjdk version "17.0.20.1" 2026-08-18
/opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar
三、依赖引入
本节说明两种引入依赖的方式(bin 包直引与 Maven 坐标),二选一即可。
3.1 方式一:bin 包直引 jar(离线)
将 core jar 与运行期依赖 commons-io 加入 classpath:
CP=/opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar:/opt/fu-lab/deps/commons-io-2.21.0.jar
echo $CP
实测回显:
/opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar:/opt/fu-lab/deps/commons-io-2.21.0.jar
3.2 方式二:Maven 坐标(在线)
在 pom.xml 中声明(Jakarta Servlet 5.0 场景,自动带入 core 与 commons-io):
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta-servlet5</artifactId>
<version>2.0.0-M5</version>
</dependency>
⚠️ 注意:Servlet 版本不同选不同坐标——Servlet 5.0 用
commons-fileupload2-jakarta-servlet5,Servlet 6.0 用-servlet6,旧 javax.servlet 用commons-fileupload2-javax;勿用已停更的commons-fileupload2-jakarta(仅到 M1)。
💡 技术拓展:FileUpload 1.x 与 2.x 的 API 差异是升级必读——
差异点 1.x 2.x 工厂创建 new DiskFileItemFactory()DiskFileItemFactory.builder().get()Servlet 上传类 ServletFileUploadJakartaServletFileUpload/JakartaServletDiskFileUploadFileItem 文件名 getFileName()getName()请求上限 setSizeMax(long)setMaxSize(long)单文件上限 setFileSizeMax(long)setMaxFileSize(long)头字符集 setHeaderEncoding(String)setHeaderCharset(Charset)本文示例全部按 2.x 的 builder + setName 风格编写。
四、核心 API 速览
本节快速梳理 2.x 的核心类与用法要点,随后两节给可运行示例。
4.1 核心类
| 类 | 职责 |
|---|---|
| DiskFileItemFactory | FileItem 工厂实现(阈值/临时目录/缓冲/字符集) |
| FileUpload(AbstractFileUpload 子类) | multipart 请求解析入口(parseRequest/parseParameterMap) |
| DiskFileItem | 解析产出的单个 part(字段或文件) |
| RequestContext | 请求抽象(ContentType/ContentLength/InputStream) |
| FileUploadException 系列 | 解析异常(含 FileUploadSizeException 等) |
4.2 典型调用链
DiskFileItemFactory.builder()链式配置(setThreshold/setRepository 等)后调用get()构建工厂;- 创建
AbstractFileUpload子类并setFileItemFactory(factory)(Servlet 场景直接用JakartaServletDiskFileUpload); upload.parseRequest(requestContext)解析出List<DiskFileItem>;- 对每个 item:
isFormField()区分字段与文件,getFieldName()取名,getString(Charset)取内容。
五、示例一:无 Web 服务器解析 multipart(core 直用)
本节给出不依赖任何 Web 服务器的完整示例:手工构造 multipart 请求体,用 core 直接解析并回显。适合在任意 Java 进程(批处理、网关)中复用解析能力。
5.1 完整代码
// FileUpload2CoreDemo.java(置于 /opt/fu-lab/src/)
import org.apache.commons.fileupload2.core.AbstractFileUpload;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.core.FileItemInputIterator;
import org.apache.commons.fileupload2.core.RequestContext;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class FileUpload2CoreDemo {
public static void main(String[] args) throws Exception {
// 1. 构造一个 multipart/form-data 请求体(一个普通字段 + 一个文件字段)
String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
String body = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"author\"\r\n\r\n"
+ "Apache Commons FileUpload 2.0.0-M5\r\n"
+ "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"report\"; filename=\"demo.txt\"\r\n"
+ "Content-Type: text/plain\r\n\r\n"
+ "hello from commons-fileupload2-core\r\n"
+ "--" + boundary + "--\r\n";
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
// 2. 实现 RequestContext 抽象请求
RequestContext ctx = new RequestContext() {
public String getCharacterEncoding() { return "UTF-8"; }
public long getContentLength() { return bytes.length; }
public String getContentType() { return "multipart/form-data; boundary=" + boundary; }
public InputStream getInputStream() { return new ByteArrayInputStream(bytes); }
public boolean isMultipartRelated() { return false; }
};
// 3. builder 构建工厂 + 解析
DiskFileItemFactory factory = DiskFileItemFactory.builder()
.setThreshold(10240)
.setCharset(StandardCharsets.UTF_8)
.get();
AbstractFileUpload<Object, DiskFileItem, DiskFileItemFactory> upload = new AbstractFileUpload<>() {
public FileItemInputIterator getItemIterator(Object request) { throw new UnsupportedOperationException(); }
public Map<String, List<DiskFileItem>> parseParameterMap(Object request) { throw new UnsupportedOperationException(); }
public List<DiskFileItem> parseRequest(Object request) { throw new UnsupportedOperationException(); }
};
upload.setFileItemFactory(factory);
upload.setMaxSize(1024L * 1024 * 10); // 整请求 10 MB
// 4. 回显解析结果
List<DiskFileItem> items = upload.parseRequest(ctx);
System.out.println("Parsed FileItem count: " + items.size());
for (DiskFileItem item : items) {
System.out.println(" fieldName=" + item.getFieldName()
+ " | isFormField=" + item.isFormField()
+ " | fileName=" + item.getName()
+ " | contentType=" + item.getContentType()
+ " | value=" + item.getString(StandardCharsets.UTF_8));
}
}
}
代码要点:RequestContext 抽象了任意来源的请求(此处为内存字节流);parseRequest(RequestContext) 是解析核心;DiskFileItem 的 getName() 在文件字段返回文件名、普通字段返回 null。
5.2 编译与运行
mkdir -p /opt/fu-lab/out
javac -encoding UTF-8 -cp "/opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar:/opt/fu-lab/deps/commons-io-2.21.0.jar" -d /opt/fu-lab/out /opt/fu-lab/src/FileUpload2CoreDemo.java
java -Xms256m -Xmx256m -cp "/opt/fu-lab/out:/opt/fu-lab/commons-fileupload2-2.0.0-M5/commons-fileupload2-core-2.0.0-M5.jar:/opt/fu-lab/deps/commons-io-2.21.0.jar" FileUpload2CoreDemo
实测回显:
Parsed FileItem count: 2
fieldName=author | isFormField=true | fileName=null | contentType=null | value=Apache Commons FileUpload 2.0.0-M5
fieldName=report | isFormField=false | fileName=demo.txt | contentType=text/plain | value=hello from commons-fileupload2-core
解析结果:author 为普通字段(isFormField=true),report 为文件(isFormField=false,文件名 demo.txt,内容 hello from commons-fileupload2-core),示例运行成功。
六、示例二:Servlet 集成(embedded Jetty + curl -F)
本节给出标准 Web 场景的集成示例:嵌入式 Jetty 起最小 Servlet,接收真实 HTTP multipart 请求并回显,用 curl -F 验证。
6.1 Maven 工程与 pom.xml
mkdir -p /opt/fu-lab/jetty-demo/src/main/java/upload && cd /opt/fu-lab/jetty-demo
pom.xml(坐标说明见三、3.2):
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta-servlet5</artifactId>
<version>2.0.0-M5</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>11.0.26</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>11.0.26</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
6.2 上传 Servlet
// /opt/fu-lab/jetty-demo/src/main/java/upload/UploadServlet.java
package upload;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.servlet5.JakartaServletDiskFileUpload;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/plain; charset=UTF-8");
PrintWriter out = response.getWriter();
try {
DiskFileItemFactory factory = DiskFileItemFactory.builder()
.setThreshold(64 * 1024)
.setCharset(StandardCharsets.UTF_8)
.get();
JakartaServletDiskFileUpload upload = new JakartaServletDiskFileUpload(factory);
upload.setMaxSize(1024L * 1024 * 10); // 整请求 10 MB
upload.setMaxFileSize(1024L * 1024 * 5); // 单文件 5 MB
upload.setHeaderCharset(StandardCharsets.UTF_8);
List<DiskFileItem> items = upload.parseRequest(request);
out.println("Parsed FileItem count: " + items.size());
for (DiskFileItem item : items) {
out.println(" fieldName=" + item.getFieldName()
+ " | isFormField=" + item.isFormField()
+ " | fileName=" + item.getName()
+ " | contentType=" + item.getContentType()
+ " | size=" + item.getSize()
+ " | value=" + item.getString(StandardCharsets.UTF_8));
}
} catch (Exception e) {
out.println("ERROR: " + e.getClass().getName() + ": " + e.getMessage());
}
}
}
要点:JakartaServletDiskFileUpload 是 2.x Jakarta 集成层的开箱即用类(内部已装配 DiskFileItemFactory 泛型);parseRequest(HttpServletRequest) 直接吃 Servlet 请求;异常统一捕获回显便于排障。
6.3 嵌入式 Jetty 启动类
// /opt/fu-lab/jetty-demo/src/main/java/upload/UploadServer.java
package upload;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
public class UploadServer {
public static void main(String[] args) throws Exception {
Server server = new Server(18080);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.addServlet(new ServletHolder(new UploadServlet()), "/upload");
server.setHandler(context);
server.start();
System.out.println("Jetty started on http://0.0.0.0:18080/upload");
server.join();
}
}
6.4 构建、启动与 curl -F 实测
cd /opt/fu-lab/jetty-demo
mvn -q -DskipTests package
mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt
printf "hello from jetty curl -F upload test\nline2: multipart parsing ok\n" > /opt/fu-lab/REPORT.txt
nohup java -Xms256m -Xmx256m -cp "target/classes:$(cat cp.txt)" upload.UploadServer > /opt/fu-lab/jetty.log 2>&1 &
sleep 6
tail -2 /opt/fu-lab/jetty.log
curl -s http://127.0.0.1:18080/upload -F "author=Apache Commons FileUpload 2.0.0-M5" -F "report=@/opt/fu-lab/REPORT.txt;type=text/plain"
实测回显:
Jetty started on http://0.0.0.0:18080/upload
Parsed FileItem count: 2
fieldName=author | isFormField=true | fileName=null | contentType=null | size=34 | value=Apache Commons FileUpload 2.0.0-M5
fieldName=report | isFormField=false | fileName=REPORT.txt | contentType=text/plain | size=65 | value=hello from jetty curl -F upload test
line2: multipart parsing ok
Servlet 完整解析 curl -F 发送的真实 multipart 请求:普通字段 author 的值(34 字节)、文件字段 REPORT.txt 的文件名/类型/字节数(65)与内容全部正确回显,集成示例验证通过。
💡 技术拓展:文件上传方案不止 Commons FileUpload——
方案 特点 适用场景 原生 Servlet Part request.getPart(),Servlet 引擎内置,零依赖简单单文件上传 Commons FileUpload 精细控制大小/存储/进度/文件数 复杂业务上传 Spring Multipart 封装成 MultipartFile,注解式开发 Spring Boot 应用 Netty 解析器 非 Servlet 栈、事件驱动 自研协议/网关 本文采用 Commons FileUpload 2.x,因其与运行环境无关、限制项最全、支持离线 jar 直引。
七、扩展:保存上传文件到磁盘
本节说明文件字段落盘的标准写法(承接 6.2 的 items 循环)。
对非表单字段,用 getInputStream() 拷贝到目标目录(此处用 java.nio 实现,避免占用大堆):
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
// 在 items 循环内:文件字段保存
if (!item.isFormField() && item.getName() != null) {
Path target = Path.of("/data/uploads", item.getName());
Files.copy(item.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
out.println(" saved to " + target);
}
说明:/data/uploads 需先创建;文件名建议再做白名单/路径穿越校验(如过滤 ../ 与反斜杠),避免覆盖任意路径。
八、常见问题
Q1:DiskFileItemFactory.builder() 与 1.x 的 new DiskFileItemFactory() 有何不同?
定位:2.x 全面改为构建器模式,返回 Builder 对象,get() 产出工厂实例;1.x 直接构造。
解决:2.x 一律 DiskFileItemFactory.builder() 链式配置各 set 方法后调用 get()。
Q2:item.getFileName() 编译报错?
定位:2.x FileItem 接口只有 getName(),1.x 的 getFileName() 已移除。
解决:改用 getName()(文件字段返回文件名,普通字段返回 null)。
Q3:Servlet 5 与 Servlet 6 运行时怎么选坐标?
定位:坐标与 Web 服务器的 Servlet 版本强相关:Tomcat 10.0/Jetty 11 为 Servlet 5 → -servlet5;Tomcat 10.1/Jetty 12 为 Servlet 6 → -servlet6;Tomcat 9 及以下 → -javax。
解决:按 Web 服务器版本引入对应坐标,混用会抛 NoClassDefFoundError。
Q4:上传中文内容乱码?
定位:字符集链路任一环不一致(表单编码、工厂 charset、getString 实参)。 解决:三处统一 UTF-8(见 6.2 示例的 setCharset + setHeaderCharset + getString(StandardCharsets.UTF_8))。
Q5:解析异常如何分类处理?
定位:2.x 异常族为 FileUploadException 子类:FileUploadSizeException(大小超限,含 getPermitted/getActualSize)、FileUploadFileCountLimitException(个数超限)、FileUploadByteCountLimitException、FileUploadContentTypeException 等。 解决:捕获后按类型返回 413/400 等 HTTP 状态,勿笼统 500。
九、参考链接
- Apache Commons FileUpload 官网:https://commons.apache.org/proper/commons-fileupload/
- 官方 bin 发布目录:https://downloads.apache.org/commons/fileupload/binaries/
- Maven Central 构件目录:https://repo1.maven.org/maven2/org/apache/commons/commons-fileupload2-core/
- Jetty 官方文档:https://jetty.org/docs/
- Jakarta Servlet 规范:https://jakarta.ee/specifications/servlet/