本文目录导读:

在Java中实现文件下载有多种方式,下面我将介绍几种常见的实现方法,包括Servlet方式、Spring Boot方式以及使用NIO的高效方式。
Servlet方式(基础实现)
@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获取要下载的文件路径
String fileName = request.getParameter("fileName");
String filePath = getServletContext().getRealPath("/uploads") + File.separator + fileName;
File file = new File(filePath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
return;
}
// 2. 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
response.setContentLengthLong(file.length());
// 3. 读取文件并写入响应流
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
}
}
Spring Boot方式(推荐)
1 使用ResponseEntity
@RestController
@RequestMapping("/api/files")
public class FileDownloadController {
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
try {
// 1. 加载文件
Path filePath = Paths.get("uploads/" + fileName);
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
// 2. 设置响应头
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"")
.body(resource);
} catch (IOException e) {
return ResponseEntity.internalServerError().build();
}
}
}
2 使用StreamingResponseBody(适合大文件)
@GetMapping("/download/large/{fileName}")
public ResponseEntity<StreamingResponseBody> downloadLargeFile(@PathVariable String fileName) {
StreamingResponseBody stream = outputStream -> {
Path filePath = Paths.get("uploads/" + fileName);
try (InputStream inputStream = Files.newInputStream(filePath)) {
IOUtils.copy(inputStream, outputStream);
}
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"")
.body(stream);
}
NIO方式(高性能)
@GetMapping("/download/nio/{fileName}")
public void downloadWithNio(@PathVariable String fileName, HttpServletResponse response)
throws IOException {
Path filePath = Paths.get("uploads/" + fileName);
FileChannel fileChannel = FileChannel.open(filePath, StandardOpenOption.READ);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentLengthLong(fileChannel.size());
// 使用FileChannel直接传输
try (FileChannel channel = fileChannel;
WritableByteChannel writableChannel = Channels.newChannel(response.getOutputStream())) {
long position = 0;
long count = channel.size();
while (position < count) {
position += channel.transferTo(position, 8192, writableChannel);
}
}
}
前端调用方式
1 使用a标签直接下载
<a href="/api/files/download/test.txt" download>下载文件</a>
2 使用JavaScript下载
function downloadFile(fileName) {
const link = document.createElement('a');
link.href = `/api/files/download/${fileName}`;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
3 使用fetch API(支持进度显示)
async function downloadWithProgress(fileName) {
const response = await fetch(`/api/files/download/${fileName}`);
const contentLength = response.headers.get('content-length');
const total = parseInt(contentLength, 10);
let loaded = 0;
const reader = response.body.getReader();
const chunks = [];
while(true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
// 更新进度
const progress = (loaded / total) * 100;
console.log(`下载进度: ${progress.toFixed(2)}%`);
}
// 合并chunks并下载
const blob = new Blob(chunks);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
}
完整的工具类
public class FileDownloadUtil {
/**
* 设置下载响应头
*/
public static void setDownloadHeaders(HttpServletResponse response,
String fileName, long fileSize) throws UnsupportedEncodingException {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("Content-Length", String.valueOf(fileSize));
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
}
/**
* 判断文件类型
*/
public static String getContentType(String fileName) {
if (fileName.endsWith(".txt")) return "text/plain";
if (fileName.endsWith(".pdf")) return "application/pdf";
if (fileName.endsWith(".doc") || fileName.endsWith(".docx"))
return "application/msword";
if (fileName.endsWith(".xls") || fileName.endsWith(".xlsx"))
return "application/vnd.ms-excel";
if (fileName.endsWith(".png")) return "image/png";
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))
return "image/jpeg";
return "application/octet-stream";
}
}
注意事项
- 大文件处理:对于大文件(>100MB),建议使用StreamingResponseBody或NIO方式
- 内存管理:避免将整个文件读入内存,使用缓冲流或管道传输
- 安全性:防止路径遍历攻击,校验文件名
- 编码问题:文件名需要进行URL编码,避免中文乱码
- 断点续传:可以支持Range头实现断点续传功能
选择哪种方式取决于你的具体需求:简单的下载用Servlet方式,Spring Boot项目使用ResponseEntity,大文件使用NIO或StreamingResponseBody。