核心概念
在 WebService 中上传文件,本质上就是客户端将文件数据作为请求的一部分发送给服务器端,服务器端接收并处理这些数据,主要有两种技术实现方式:

-
SOAP (Simple Object Access Protocol) with MTOM (Message Transmission Optimization Mechanism)
- SOAP: 一种基于 XML 的协议,用于在 Web 上交换结构化信息,传统的 SOAP 消息是纯文本的,如果直接将二进制文件(如图片、视频)编码成 Base64 嵌入 XML 中,会导致消息体积急剧增大,效率低下。
- MTOM: 是 SOAP 1.2 规范的一部分,专门用于解决二进制数据传输的效率问题,它的工作原理是:
- 文件数据不会被编码到 XML 主消息体中。
- 文件数据作为独立的 MIME 部分(
MimePart)附加在 SOAP 消息之后。 - SOAP 消息中只包含一个指向这个 MIME 部分的引用(一个 Content-ID)。
- 优点: 标准化、安全、可靠,适合企业级应用,对于大文件传输,效率远高于 Base64。
- 缺点: 配置相对复杂,消息结构比 REST 更臃肿。
-
REST (Representational State Transfer)
- REST: 一种更轻量级的架构风格,通常通过 HTTP 协议直接传输数据。
- 文件上传通常使用
multipart/form-data格式的 POST 请求,这与在传统 Web 表单中上传文件的方式完全相同。 - 优点: 简单、灵活、易于调试(可以直接用浏览器或 Postman 测试)、性能通常更好。
- 缺点: 无状态,安全性需要自己实现(如使用 Token 认证),不如 SOAP 规范严谨。
选择建议:
- 如果你的项目已经基于 SOAP 架构,或者需要 WS-Security 等高级 SOAP 特性,请选择 MTOM。
- 如果你在构建新的、现代的 API,或者更看重开发效率和简洁性,REST 是更主流和推荐的选择。
使用 JAX-WS + MTOM 实现文件上传 (SOAP)
这里我们使用 JAX-WS(Java API for XML Web Services),这是 Java 标准的 SOAP 实现,我们将使用 Metro 作为实现框架(通常随 JDK 一起提供)。

服务端实现
步骤 1: 创建 Maven 项目
在 pom.xml 中添加依赖:
<dependencies>
<!-- JAX-WS API -->
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- JAX-WS RI (Metro) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.3</version>
</dependency>
<!-- 用于测试服务器 -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.44.v20250927</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.44.v20250927</version>
</dependency>
</dependencies>
步骤 2: 创建数据传输对象
我们需要一个类来接收文件信息,使用 @MTOM 注解是关键,它告诉 JAX-WS 运行时启用 MTOM 优化。

import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlSeeAlso({ObjectFactory.class})
public class FileTransferRequest {
private String fileName;
private String fileType;
// 使用 DataHandler 来处理二进制数据
// @XmlMimeType 指定了文件的 MIME 类型
@XmlMimeType("application/octet-stream")
private DataHandler fileData;
// Getters and Setters
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public DataHandler getFileData() {
return fileData;
}
public void setFileData(DataHandler fileData) {
this.fileData = fileData;
}
}
步骤 3: 创建 WebService 接口
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.soap.MTOM;
// @MTOM 注解启用 MTOM
@MTOM
// @WebService 指定这是一个 WebService
@WebService
// @SOAPBinding.Style.DOCUMENT 表示使用文档风格的 SOAP 消息
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT)
public interface FileUploadService {
@WebMethod
String uploadFile(FileTransferRequest request);
}
步骤 4: 实现 WebService
import javax.activation.DataHandler;
import javax.jws.WebService;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@WebService(endpointInterface = "com.example.FileUploadService")
public class FileUploadServiceImpl implements FileUploadService {
@Override
public String uploadFile(FileTransferRequest request) {
String fileName = request.getFileName();
DataHandler dataHandler = request.getFileData();
// 定义服务器上保存文件的目录
String uploadDir = "uploads/";
File uploadDirFile = new File(uploadDir);
if (!uploadDirFile.exists()) {
uploadDirFile.mkdirs();
}
String filePath = uploadDir + fileName;
try (InputStream is = dataHandler.getInputStream();
OutputStream out = new FileOutputStream(filePath)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("文件上传成功: " + filePath);
return "文件 " + fileName + " 上传成功!保存在: " + filePath;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败: " + e.getMessage();
}
}
}
步骤 5: 发布 WebService
我们可以使用一个简单的 Jetty 服务器来发布这个服务。
import com.sun.xml.ws.transport.http.servlet.WSServlet;
import com.sun.xml.ws.transport.http.servlet.WSServletContextListener;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import javax.xml.ws.Endpoint;
import java.io.File;
public class FileUploadPublisher {
public static void main(String[] args) throws Exception {
// 1. 发布方式一:使用 Endpoint.publish (简单,适合测试)
/*
String address = "http://localhost:8080/FileUploadService";
FileUploadService serviceImpl = new FileUploadServiceImpl();
Endpoint.publish(address, serviceImpl);
System.out.println("SOAP Service 已发布在: " + address);
System.out.println("按任意键停止服务...");
System.in.read();
*/
// 2. 发布方式二:使用 Servlet 容器 (如 Jetty, Tomcat,更灵活)
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// 添加 WSServlet
ServletHolder holder = new ServletHolder(new WSServlet());
holder.setName("FileUploadService");
// 指定实现类
holder.setInitParameter("javax.xml.ws.servlet.endpoint-class", "com.example.FileUploadServiceImpl");
context.addServlet(holder, "/FileUploadService/*");
// 添加监听器
context.addEventListener(new WSServletContextListener());
server.start();
System.out.println("SOAP Service 已发布在: http://localhost:8080/FileUploadService");
System.out.println("按 Ctrl+C 停止服务...");
server.join();
}
}
注意: 要让 WSServlet 正常工作,你需要在 WEB-INF/web.xml 中配置,但使用 Metro 的 WSServletContextListener 可以自动完成大部分配置,上面的代码是简化的。
使用 JAX-RS (REST) 实现文件上传
这里我们使用 JAX-RS(Java API for RESTful Web Services),标准实现是 Jersey。
服务端实现
步骤 1: 创建 Maven 项目
在 pom.xml 中添加依赖:
<dependencies>
<!-- Jersey 核心依赖 -->
<dependency> 