You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring接收form-data文件性能不佳,求Java高效接收方案

优化Spring中form-data多文件上传性能的方案

我在Java中寻找高效接收多文件的方式,使用form-data接收时发现Spring的额外处理导致网络上传时性能显著下降,本地测试影响不大。测试了4种接口(3种基于multipart/form-data,1种二进制),相同文件下二进制上传速度几乎是form-data的2倍。测试环境:Java 17、Gradle 8.4、Spring 3.2.0。

测试用的4个接口代码如下:

@SneakyThrows
@PostMapping(value = "upload/formData/v1", consumes = "multipart/form-data")
public void uploadFileUsingFormData(@RequestParam Map<String, MultipartFile> files) {
    log.info("Received request, started downloading");
    for (Map.Entry<String, MultipartFile> entry : files.entrySet()) {
        var watch = new StopWatch();
        watch.start();
        var bytes = entry.getValue().getBytes();
        watch.stop();
        log.info("Got file with key: {}, size: {}, time spent: {} ms", entry.getKey(), bytes.length, watch.getTotalTimeMillis());
    }
    log.info("----------------------------------------");
}

@SneakyThrows
@PostMapping(value = "upload/formData/v2", consumes = "multipart/form-data")
public void uploadFileUsingFormDatav2(HttpServletRequest request) {
    log.info("Received request, started downloading");
    Collection<Part> parts = request.getParts();
    for (Part part : parts) {
        var watch = new StopWatch();
        watch.start();
        var bytes = part.getInputStream().readAllBytes();
        watch.stop();
        log.info("Got file with key: {}, size: {}, time spent: {} ms", part.getName(), bytes.length, watch.getTotalTimeMillis());
    }
    log.info("----------------------------------------");
}

@SneakyThrows
@PostMapping(value = "upload/formData/v3", consumes = "multipart/form-data")
public void uploadFileUsingFormDatav3(MultipartHttpServletRequest request) {
    log.info("Received request, started downloading");
    Collection<Part> parts = request.getParts();
    for (Part part : parts) {
        var watch = new StopWatch();
        watch.start();
        var bytes = part.getInputStream().readAllBytes();
        watch.stop();
        log.info("Got file with key: {}, size: {}, time spent: {} ms", part.getName(), bytes.length, watch.getTotalTimeMillis());
    }
    log.info("----------------------------------------");
}

@PostMapping (value = "upload/binary")
public void acceptData(InputStream dataStream) throws Exception {
    log.info("Received request, started downloading");

    var watch = new StopWatch();
    watch.start();
    var bytes = dataStream.readAllBytes();
    watch.stop();
    log.info("Got file, size: {}, time spent: {} ms", bytes.length, watch.getTotalTimeMillis());

    log.info("----------------------------------------");
}

测试结果:

  • 二进制上传测试:单文件上传耗时约127ms
  • form-data上传测试:相同文件上传耗时约238ms,v2和v3接口表现一致

以下是针对性的优化建议:

1. 调整Spring Multipart配置,减少磁盘IO开销

Spring默认会将超过阈值的Multipart文件写入临时磁盘文件,这会带来额外的IO延迟。可以通过配置调高内存存储阈值,或使用更快的临时存储介质:

spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 500MB
      file-size-threshold: 10MB  # 超过该大小才写入磁盘,根据文件规模调整
      location: /dev/shm         # Linux环境下使用RAM磁盘作为临时目录,避免物理磁盘IO

2. 采用流式处理,避免一次性加载全量数据

当前代码中readAllBytes()会将整个文件加载到内存,大文件场景下既占用内存又增加耗时。建议直接将输入流写入目标存储,跳过内存缓存步骤:

@SneakyThrows
@PostMapping(value = "upload/formData/optimized", consumes = "multipart/form-data")
public void uploadFileUsingFormDataOptimized(HttpServletRequest request) {
    log.info("Received request, started streaming");
    Collection<Part> parts = request.getParts();
    for (Part part : parts) {
        var watch = new StopWatch();
        watch.start();
        // 直接流式写入目标位置,避免内存缓存
        try (InputStream in = part.getInputStream();
             OutputStream out = new FileOutputStream("/your/target/path/" + part.getName())) {
            in.transferTo(out);
        }
        watch.stop();
        log.info("Streamed file with key: {}, size: {}, time spent: {} ms", 
                 part.getName(), part.getSize(), watch.getTotalTimeMillis());
    }
    log.info("----------------------------------------");
}

3. 替换Multipart解析器为Apache Commons FileUpload

Spring默认的StandardServletMultipartResolver在某些场景下性能不如Apache Commons FileUpload的实现,可以尝试更换:

首先添加Gradle依赖:

implementation 'commons-fileupload:commons-fileupload:1.5'

然后配置自定义解析器:

@Configuration
public class MultipartConfig {
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(524288000); // 500MB
        resolver.setMaxUploadSizePerFile(104857600); // 100MB
        resolver.setDefaultEncoding("UTF-8");
        resolver.setMaxInMemorySize(10485760); // 10MB内存阈值
        return resolver;
    }
}

4. 启用HTTP/2提升多文件并发性能

HTTP/2支持多路复用,可减少TCP连接开销,大幅提升多文件上传的并发效率。Spring Boot中启用HTTP/2只需配置SSL:

server:
  http2:
    enabled: true
  ssl:
    enabled: true
    key-store: classpath:keystore.p12
    key-store-password: your-password
    key-store-type: PKCS12

5. 分块上传+异步并行处理(适合大文件场景)

对于大文件批量上传,可实现分块上传逻辑,将单个文件拆分为多个小块并行上传,后端通过@Async异步合并,提升整体吞吐量。


内容的提问来源于stack exchange,提问作者Eugene Kyrychenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:20:54