Commons Fileupload 2.x 迁移:设置仓库和阈值
2025-03-12 11:12:21
如何在迁移到 Apache Commons Fileupload 2.x 时设置仓库和文件阈值?
老项目升级,挺麻烦的。最近我就碰到了一个,要把一个 Java 11 项目升级到 Java 17。 旧版本用的是 javax.servlet.*
,所以得升级到 jakarta.servlet.*
。
升级过程中,DiskFileItemFactory
这块儿把我给整懵了。
旧代码是这样的:
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, dir));
到了 2.x 版本,构造函数变成私有的了,只能这么写:
ServletFileUpload upload = new JakartaServletFileUpload(DiskFileItemFactory.builder().get());
但是,DiskFileItemFactory.Builder
居然没有 setRepository
和 setThreshold
方法! 这可咋整?我知道这个 repository 就是个临时文件夹,看了下 2.x 版本的 DiskFileItemFactory.Builder.get()
的 API,上面写着:
Constructs a new instance. This builder use the aspects Path and buffer size. You must provide an origin that can be converted to a Reader by this builder, otherwise, this call will throw an UnsupportedOperationException.
大致意思就是,这个 Builder 使用了 “aspect” 的 Path 和 buffer size。得提供一个能被转换成 Reader 的 origin,不然就报错。
问题来了,这个 “ASPECT” 到底是啥玩意? 虽说这个路径只是用来放临时文件的,但 DiskFileItemFactory
本身又有 .getRepository()
和 .getThreshold()
方法,我还是想设置一下,毕竟旧代码里设置了,说不定很重要呢。
DiskFileItemFactory.Builder
倒是从 org.apache.commons.io.build.AbstractOriginSupplier
继承了 .setPath()
方法,也从 org.apache.commons.io.build.AbstractStreamBuilder
继承了 .setBufferSize()
和 .setBufferSizeMax()
方法。但是,这些方法是不是对应之前的 setRepository()
和 setThreshold()
,还真不好说。这些类的 API 太抽象了,啥也没说清楚。.setPath()
方法只说了 “sets a new origin”,别的啥也没提。
所以,我就想搞清楚:
DiskFileItemFactory.Builder.get()
里的 “ASPECT” 到底是个啥?- Path 和 BufferSize 是不是跟之前的 Respository 和 Threshold 一回事?
问题根源:API 设计变更
问题的根源在于 Apache Commons Fileupload 从 1.x 版本到 2.x 版本,DiskFileItemFactory
的 API 发生了较大变化。 主要目的是为了:
- 简化 API: 通过 Builder 模式,隐藏了对象创建的复杂性。
- 增强灵活性: 通过
AbstractOriginSupplier
和AbstractStreamBuilder
提供更通用的配置方式。 - 与 Jakarta EE 兼容: 为了适配 Jakarta EE,包名从
javax
变更为jakarta
。
但是,这种变化也带来了迁移成本,让开发者需要重新理解 API 的用法。
解决方案
咱们一个一个来解决:
1. "ASPECT" 是什么?
“ASPECT” 并不是一个特定的类或接口,而是一个抽象的概念,指的是构建 DiskFileItemFactory
实例所需的配置信息来源。 实际上,这个 “ASPECT” 可以是任何实现了 FileItemFactoryBuilder
接口的对象.
我们可以通过提供不同的 builder 方式去获得FileItemFactory. 在使用默认情况下,使用的构建的builder会追溯到 AbstractOriginSupplier
.
当没有指定构建时使用的类(Builder), 可以通过.get() 得到FileItemFactory
FileItemFactory<?> factory = DiskFileItemFactory.builder().get()
该实现相当于提供了 DiskFileItemFactory.Builder
作为其 ASPECT, 会被AbstractOriginSupplier<Path, ?>
中的getPath()
实现中转换为Path
,并提供了 java.nio.file.Files;
中的系统临时目录作为 Path。而 BufferSizeMax和 BufferSize 都会被设置为默认值
2. Path 和 BufferSize 与 Repository 和 Threshold 的关系
- Path 与 Repository: 基本等价。
Path
指定了临时文件存放的目录,与之前的Repository
功能一致。 在DiskFileItemFactory
中, 如果你调用.getRepository()
函数,则实际上是返回的DiskFileItemFactory
所拥有的 Path. - BufferSize 与 Threshold: 不完全等价,但是强相关。
Threshold
(阈值)在旧版本中是指:如果上传的文件大小超过这个阈值,就写入磁盘,否则保存在内存里。BufferSize
在新的设置中并非完全直接对应Threshold
, 但是依旧担任如果文件过大时,将数据写入磁盘时,缓冲区的大小, 它的大小也直接影响写入效率和内存占用。
虽然不等价, 但是依旧具有相关性. 所以说,之前的代码是有关联性, 但是也应该进行一定更新的. 尤其是需要根据实际场景合理调整缓冲区大小。
具体的代码修改方法
既然弄明白了原理,改起代码来就简单了。以下提供几种方案:
方案一:使用 setPath
和 setBufferSize
import java.nio.file.Paths;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
// ...
// 假设你需要设置的临时目录和阈值
String tempDir = "/path/to/temp/dir"; // 或者使用 Paths.get(...)
int bufferSize = 4096; // 设置合适的缓冲区大小
DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder();
// 设置临时目录
builder.setPath(Paths.get(tempDir)); //原有的Repository
// 设置缓冲区大小,可以根据需求设置合适的 Threshold
builder.setBufferSize(bufferSize); //原有的 Threshold
DiskFileItemFactory fileItemFactory = builder.get();
JakartaServletFileUpload upload = new JakartaServletFileUpload(fileItemFactory);
// ... 使用 upload 对象处理文件上传 ...
原理:
setPath(Paths.get(tempDir))
:设置临时文件存放的目录。setBufferSize(bufferSize)
:间接控制文件写入磁盘的时机, 设置一个你认为适合的值。
安全建议:
- 临时目录权限: 确保应用程序对临时目录有读写权限。
- 定期清理: 定期清理临时目录,防止磁盘空间耗尽。
方案二:继承 DiskFileItemFactory.Builder
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
// 自定义 Builder
class MyDiskFileItemFactoryBuilder extends DiskFileItemFactory.Builder {
private Path repository;
private int threshold;
public MyDiskFileItemFactoryBuilder(Path repository, int threshold) {
this.repository = repository;
this.threshold = threshold;
}
@Override
public DiskFileItemFactory get() {
setPath(repository);
setBufferSize(threshold); // 将 threshold 设置为 bufferSize
return super.get();
}
}
// ...
//使用
Path tempDir = Paths.get("/path/to/temp");
int threshold = 1024 * 10; // 10KB
MyDiskFileItemFactoryBuilder myBuilder = new MyDiskFileItemFactoryBuilder(tempDir, threshold);
DiskFileItemFactory factory = myBuilder.get();
JakartaServletFileUpload upload = new JakartaServletFileUpload(factory);
原理
- 直接了当, 我们自定义了构建函数, 在get() 时分别使用了 setPath()和setBufferSize()设置了对应参数.
方案三: 使用 Supplier 构建DiskFileItemFactory(进阶)
通过 Supplier 来自定义, 可以进一步精细化 FileItemFactory
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Supplier;
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
//...
//使用
Supplier<DiskFileItemFactory.Builder> supplier =
() -> DiskFileItemFactory.builder()
.setPath(Paths.get("你的临时目录"))
.setBufferSize(4096); // 例如,4KB 缓冲区
// 现在, 你每次要获取工厂的时候, 都可以获取对应的 Factory, 其均满足我们提前定义好的Path 和 BufferSize.
// 这就解决了 ASPECT 不明确的问题.
DiskFileItemFactory fileItemFactory = supplier.get().get();
JakartaServletFileUpload upload = new JakartaServletFileUpload(fileItemFactory);
原理
- 在工厂方法中指定了参数, 使用时则使用定义的参数。
安全建议:
同方案一。
总结一下,升级到 Apache Commons Fileupload 2.x,虽然 API 变了,但只要弄明白 Path 和 BufferSize 的作用,就能轻松搞定临时目录和阈值的设置。 实在不行,咱还可以自定义 Builder,想咋设置就咋设置。关键是理解背后的原理,对症下药.