文件上传的本质是,浏览器将用户选中的文件数据通过 HTTP 协议打包成一个特殊的请求(multipart/form-data
格式),发送给服务器,服务器端的 Java 程序接收到这个请求后,解析出文件数据,并将其保存到服务器的指定位置。
整个过程如下图所示:
sequenceDiagram participant U as 用户/浏览器 participant S as Java服务器 U->>U: 1. 选择文件,点击提交 Note over U: 表单数据被编码为<br>multipart/form-data U->>S: 2. 发送 HTTP POST 请求<br>(包含文件数据流) Note over S: 3. Servlet 容器接收请求 S->>S: 4. 解析请求流<br>(使用Apache Commons FileUpload等库) S->>S: 5. 将文件流写入磁盘 S->>U: 6. 返回上传结果(成功/失败)
前端主要通过 HTML 表单来完成。
<form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="myfile"> <input type="submit" value="上传文件"> </form>
关键点在于表单的三个属性:
method="post"
文件内容通常较大,必须使用 POST 方法将数据放在请求体内发送。
enctype="multipart/form-data"
这是最重要的部分,它告诉浏览器如何对表单数据进行编码。
multipart/form-data
编码格式是怎样的?
当表单被提交时,浏览器不会像普通表单(application/x-www-form-urlencoded
)那样把所有数据编码成键值对,相反,它会将整个请求体分割成多个“部分”(Part),每个部分对应一个表单字段(比如文件字段或普通文本字段),每个部分之间用一个随机生成的“边界字符串”(Boundary)隔开。
一个简化后的 HTTP 请求体看起来是这样的:
POST /upload HTTP/1.1 Host: example.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="myfile"; filename="example.txt" Content-Type: text/plain (这里是 example.txt 文件的二进制或文本内容) ------WebKitFormBoundary7MA4YWxkTrZu0gW
boundary
定义了分隔符。
每个部分都有描述信息的头(如Content-Disposition
,包含了字段名和文件名)。
之后是两个换行,接着是该部分的数据内容。
后端的主要任务是解析这个复杂的multipart/form-data
请求,从中提取出文件流和普通表单字段。
a) 原始 Servlet API (3.0 之前) - 需要第三方库
在 Servlet 3.0 规范之前,标准 API 没有直接解析multipart
请求的功能,开发者必须依赖第三方库,最著名的是Apache Commons FileUpload。
原理 这些库会读取HttpServletRequest
的输入流(ServletRequest.getInputStream()
),然后根据Content-Type
中的boundary
字符串,手动切割和解析每一个部分。
流程
1. 检查请求是否是multipart/form-data
类型。
2. 创建一个磁盘工厂(DiskFileItemFactory
)和文件上传处理器(ServletFileUpload
)。
3. 调用ServletFileUpload.parseRequest(request)
方法,将请求解析成一个List<FileItem>
。
4. 遍历这个列表,判断每个FileItem
是普通字段还是文件字段。
普通字段 直接调用getString()
获取值。
文件字段 调用getInputStream()
读取文件流,然后使用 I/O 操作(如Files.copy
)将流写入到服务器的硬盘上。
示例代码(使用 Apache Commons FileUpload):
// 检查请求是否包含文件上传 if (ServletFileUpload.isMultipartContent(request)) { DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { if (item.isFormField()) { // 普通文本字段 String fieldName = item.getFieldName(); String value = item.getString(); // ... 处理文本数据 } else { // 文件字段 String fileName = item.getName(); InputStream fileContent = item.getInputStream(); // 将文件保存到服务器 Files.copy(fileContent, Paths.get("/upload/path/", fileName)); } } }
b) 标准 Servlet API (3.0 及以上) - 推荐使用
Servlet 3.0 规范将文件上传功能纳入了标准,大大简化了操作,你不再需要额外的 JAR 包。
原理 在 Servlet 上使用@MultipartConfig
注解后,容器(如 Tomcat、Jetty)会自动帮你解析multipart
请求。
核心接口javax.servlet.http.Part
流程
1. 在 Servlet 类上添加@MultipartConfig
注解。
2. 通过HttpServletRequest.getPart(String name)
获取指定名称的文件部分,或者用getParts()
获取所有部分。
3. 通过Part
对象的方法轻松获取文件名、输入流等。
示例代码(使用 Servlet 3.0+ Part API):
@WebServlet("/upload") @MultipartConfig // 关键注解,告诉容器此Servlet处理文件上传 public class FileUploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) { try { // 获取文件部分 Part filePart = request.getPart("myfile"); // "myfile" 是前端input的name String fileName = filePart.getSubmittedFileName(); // 将文件保存到服务器 InputStream fileContent = filePart.getInputStream(); Files.copy(fileContent, Paths.get("/upload/path/", fileName)); response.getWriter().print("上传成功!"); } catch (Exception e) { e.printStackTrace(); response.getWriter().print("上传失败!"); } } }
c) Spring MVC 框架 - 最常用的方式
在 Spring MVC 中,文件上传被进一步封装,变得极其简单,它底层使用的也是 Servlet 3.0 的 API 或者 Commons FileUpload。
原理 你需要配置一个MultipartResolver
Bean(通常是StandardServletMultipartResolver
,基于 Servlet 3.0)。
流程
1. 在控制器方法中,使用@RequestParam("file") MultipartFile file
参数来接收文件。
2. 直接调用MultipartFile
的方法进行操作,如transferTo(File dest)
。
示例代码(使用 Spring MVC):
@Controller public class FileUploadController { @PostMapping("/upload") public String handleFileUpload(@RequestParam("myfile") MultipartFile file) { if (!file.isEmpty()) { try { // 一行代码完成保存 file.transferTo(new File("/upload/path/" + file.getOriginalFilename())); return "redirect:/success"; } catch (IOException e) { e.printStackTrace(); } } return "redirect:/error"; } }
环节 | 核心要点 |
前端 | 使用 将文件数据打包成带有boundary 分隔符 的 HTTP 请求体。 |
传输 | 通过 HTTP POST 协议,将包含文件二进制流的请求发送到服务器。 |
后端解析 | 核心任务是解析multipart 格式的请求流,分离出文件部分和普通字段。 |
后端实现 | 1.Servlet 3.0+: 使用@MultipartConfig 和Part 接口(标准,推荐)。2.第三方库: 使用 Apache Commons FileUpload(传统方式)。 3.Spring MVC: 使用 MultipartResolver 和MultipartFile 接口(最简洁,企业级首选)。 |
存储 | 获取文件部分的输入流(InputStream ),通过 I/O 操作将流写入服务器的文件系统或对象存储。 |
希望这个从原理到实现的详细解释能帮助你彻底理解 Java 文件上传的机制!
文章摘自:https://idc.huochengrm.cn/js/16357.html
评论