挑戰(zhàn)大文件傳輸極限!基于兩種技術(shù)棧的 Spring Boot 流式處理實戰(zhàn)
在現(xiàn)代企業(yè)級 Web 系統(tǒng)中,上傳或下載大于 100MB 的文件早已成為標(biāo)配,如上傳日志、視頻、數(shù)據(jù)庫備份,或從服務(wù)器分發(fā)大文件。但傳統(tǒng)做法通常會將整個文件加載到內(nèi)存中處理,這樣做的結(jié)果是:
- 內(nèi)存消耗大;
- 性能下降;
- 并發(fā)能力差;
- 大概率觸發(fā) OOM。
Spring Boot 針對這一挑戰(zhàn),提供了兩種不同的流式處理技術(shù)路徑:
- 基于傳統(tǒng) Servlet 架構(gòu)的阻塞式流式處理(適合大多數(shù)場景);
- 基于 WebFlux 響應(yīng)式模型的非阻塞處理(適合高并發(fā)環(huán)境、云平臺)。
本篇文章將從上傳與下載兩個方面,詳細(xì)剖析這兩種架構(gòu)下的處理方式,并輔以前端 Thymeleaf + Bootstrap 頁面整合,構(gòu)建可直接實戰(zhàn)部署的文件服務(wù)能力。
后端實現(xiàn)
文件上傳:流式處理雙架構(gòu)對比
傳統(tǒng)阻塞模型上傳(基于 Servlet 的 InputStream)
- 使用 MultipartFile 獲取上傳內(nèi)容;
- 通過 InputStream 讀取上傳數(shù)據(jù)并寫入 OutputStream;
- 實現(xiàn)方式清晰,適合文件不大的常規(guī)業(yè)務(wù)系統(tǒng)。
路徑:src/main/java/com/icoderoad/controller/FileUploadController.java
package com.icoderoad.controller;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.*;
@RestController
@RequestMapping("/file")
public class FileUploadController {
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
Path path = Paths.get("/data/uploads/", file.getOriginalFilename());
try (InputStream in = file.getInputStream();
OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE)) {
byte[] buffer = new byte[8192]; // 分塊讀取,防止內(nèi)存暴漲
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("上傳失敗:" + e.getMessage());
}
return ResponseEntity.ok("上傳成功!");
}
}
響應(yīng)式模型上傳(基于 WebFlux 的 Flux)
- 使用
Flux<DataBuffer>
讀取上傳流; - 每塊數(shù)據(jù)到達(dá)就立即寫入磁盤;
- 完全非阻塞,適合海量并發(fā)上傳。
路徑:src/main/java/com/icoderoad/webflux/FileReactiveUploadHandler.java
package com.icoderoad.webflux;
import org.springframework.core.io.buffer.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.file.*;
@RestController
@RequestMapping("/file")
public class FileReactiveUploadHandler {
@PostMapping(value = "/upload/reactive", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> uploadReactive(@RequestPart("file") Flux<DataBuffer> fileBuffer,
@RequestPart("fileName") String fileName) {
Path path = Paths.get("/data/uploads/", fileName);
return DataBufferUtils.write(fileBuffer, path, StandardOpenOption.CREATE).then();
}
}
注意:若你的工程中同時引入了
spring-boot-starter-web
和spring-boot-starter-webflux
,需添加如下配置防止沖突:
spring:
main:
web-application-type: reactive
文件下載:流式返回雙架構(gòu)詳解
Servlet 模型下載(InputStreamResource 或 OutputStream)
方式一:InputStreamResource 下載(推薦)
路徑:src/main/java/com/icoderoad/controller/FileDownloadController.java
@GetMapping("/download")
public ResponseEntity<InputStreamResource> download() throws IOException {
Path path = Paths.get("/data/packages/demo-installer.exe");
InputStreamResource resource = new InputStreamResource(Files.newInputStream(path));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=demo-installer.exe")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
方式二:OutputStream 手動寫入(適合控制更細(xì)粒度)
@GetMapping("/download/stream")
public void downloadStream(HttpServletResponse response) throws IOException {
Path path = Paths.get("/data/packages/demo-installer.exe");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=demo-installer.exe");
try (InputStream in = Files.newInputStream(path);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
WebFlux 響應(yīng)式下載(Flux)
路徑:src/main/java/com/icoderoad/webflux/FileReactiveDownloadHandler.java
@GetMapping(value = "/download/reactive", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Mono<Void> downloadReactive(ServerHttpResponse response) {
Path filePath = Paths.get("/data/packages/demo-installer.exe");
Flux<DataBuffer> fileStream = DataBufferUtils.read(filePath, new DefaultDataBufferFactory(), 8192);
response.getHeaders().add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=demo-installer.exe");
return response.writeWith(fileStream);
}
前端頁面集成:Thymeleaf + Bootstrap 實現(xiàn)上傳下載
上傳頁面:src/main/resources/templates/upload.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>大文件上傳 - Spring Boot 流式處理</title>
<meta charset="UTF-8">
<link rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white text-center">
<h4>大文件上傳</h4>
</div>
<div class="card-body">
<form id="uploadForm" enctype="multipart/form-data">
<div class="mb-3">
<label for="file" class="form-label">選擇要上傳的文件</label>
<input type="file" class="form-control" name="file" id="file" required>
</div>
<button type="submit" class="btn btn-success w-100">開始上傳</button>
</form>
<div id="uploadResult" class="mt-3 alert d-none" role="alert"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const form = document.getElementById("uploadForm");
const resultBox = document.getElementById("uploadResult");
form.addEventListener("submit", function (e) {
e.preventDefault();
const formData = new FormData(form);
resultBox.className = "mt-3 alert alert-info";
resultBox.innerText = "上傳中,請稍候...";
fetch("/file/upload", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(message => {
resultBox.className = "mt-3 alert alert-success";
resultBox.innerText = message;
})
.catch(error => {
resultBox.className = "mt-3 alert alert-danger";
resultBox.innerText = "上傳失敗:" + error.message;
});
});
</script>
</body>
</html>
下載頁面:src/main/resources/templates/download.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>大文件下載 - Spring Boot 流式處理</title>
<meta charset="UTF-8">
<link rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h4>下載大文件</h4>
</div>
<div class="card-body">
<p class="lead">點擊按鈕即可開始下載服務(wù)端的大文件</p>
<a href="/file/download" class="btn btn-primary btn-lg w-100">
下載文件(阻塞式)
</a>
<a href="/file/download/reactive" class="btn btn-outline-success btn-lg w-100 mt-3">
下載文件(響應(yīng)式)
</a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
技術(shù)架構(gòu)比較與適用建議
類型 | 技術(shù)方案 | 優(yōu)勢 | 適用場景 |
上傳 | MultipartFile | 簡單易用 | 小文件、表單上傳 |
上傳 | InputStream | 內(nèi)存控制優(yōu) | 中大型文件、低并發(fā)場景 |
上傳 | Flux | 非阻塞、內(nèi)存占用極低 | 高并發(fā)上傳服務(wù)、云函數(shù) |
下載 | InputStreamResource | 快速、穩(wěn)定 | 一般大文件下載 |
下載 | OutputStream | 緩沖可調(diào)、可控性強(qiáng) | 定制化響應(yīng)、流控制需求 |
下載 | Flux | 非阻塞高性能 | 視頻流、云平臺大規(guī)模文件分發(fā) |
結(jié)語:選擇合適技術(shù)棧,跑得更快更遠(yuǎn)
對于需要傳輸大文件的系統(tǒng)而言,Spring Boot 提供了“阻塞”與“響應(yīng)式”兩種武器——前者成熟穩(wěn)定,適合常規(guī)系統(tǒng);后者輕盈高效,天生適配現(xiàn)代云架構(gòu)與高并發(fā)負(fù)載。
通過合理配置與實現(xiàn):
- 你可以輕松上傳/下載 GB 級別的文件;
- 內(nèi)存占用控制在最小;
- 性能瓶頸被徹底打破。