在现代微服务架构中,RESTful API 是主流,但很多遗留系统(Legacy Systems)或第三方服务仍然使用 SOAP 协议。当我们想要重构系统或为前端提供标准 REST 接口时,就需要在两者之间架起一座桥梁。
本文将手把手教你如何使用 Spring Boot 构建一个“适配器层”,将传统的 SOAP 服务封装并转换为现代的 RESTful 接口。
🏗️ 核心架构思路
我们的目标不是重写 SOAP 服务,而是创建一个代理层(Adapter Layer)。
- REST 控制器:接收前端的 JSON 请求。
- 转换逻辑:将 JSON 参数转换为 SOAP 请求对象。
- SOAP 客户端:调用旧的 Web Service。
- 响应映射:将 SOAP 返回的 XML 对象转换为 JSON DTO 返回给前端。
🛠️ 第一步:引入依赖
在 pom.xml 中,我们需要引入 Spring Web(用于 REST)和 Spring Web Services(用于 SOAP 客户端支持)。
<dependencies>
<!-- 用于开发 REST 接口 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用于调用 SOAP 服务 (JAX-WS 支持) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- Lombok (可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
在实际项目中,SOAP 的类通常是根据 WSDL 自动生成的。为了演示,我们手动模拟两个类:一个是 SOAP 服务端返回的复杂对象,一个是 REST 客户端想要的简洁对象。
1. 模拟 SOAP 服务端返回的对象
package com.example.soapclient;
import jakarta.xml.bind.annotation.*;
// 模拟 SOAP 响应中的复杂对象
@XmlRootElement(name = "UserResponse", namespace = "http://example.com/soap")
public class SoapUserResponse {
private String userId;
private String fullName;
private String emailAddress;
// 构造函数、Getter 和 Setter 省略 (实际开发中由 IDE 生成或 Lombok)
public SoapUserResponse() {}
public SoapUserResponse(String userId, String fullName, String emailAddress) {
this.userId = userId;
this.fullName = fullName;
this.emailAddress = emailAddress;
}
// ... getters and setters
public String getUserId() { return userId; }
public String getFullName() { return fullName; }
public String getEmailAddress() { return emailAddress; }
}
2. 定义 REST 接口返回的 DTO
package com.example.restapi;
// REST 客户端接收的简洁 JSON 对象
public class UserDTO {
private String id;
private String name;
// 注意:SOAP 中有 email,但 REST 接口决定不暴露这个字段,实现数据脱敏
public UserDTO() {}
public UserDTO(String id, String name) {
this.id = id;
this.name = name;
}
// ... getters and setters
public String getId() { return id; }
public String getName() { return name; }
}
这个组件负责真正去调用旧的 Web Service。在实际场景中,这里会使用 WebServiceTemplate 进行 XML 通信。
package com.example.soapclient;
import org.springframework.stereotype.Component;
import org.springframework.ws.client.core.WebServiceTemplate;
@Component
public class LegacySoapClient {
private final WebServiceTemplate webServiceTemplate;
public LegacySoapClient(WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
/**
* 模拟调用远程 SOAP 服务获取用户信息
*/
public SoapUserResponse getUserFromSoapService(String userId) {
System.out.println(">>> 正在调用 SOAP 服务,获取用户 ID: " + userId);
// 【实际代码示例】:
// return (SoapUserResponse) webServiceTemplate.marshalSendAndReceive(
// "http://localhost:8080/soap-service",
// new GetUserRequest(userId)
// );
// 这里直接返回一个模拟的 SOAP 响应对象用于演示
return new SoapUserResponse(userId, "张三 (来自 SOAP)", "zhangsan@old-system.com");
}
}
这是最关键的一步。我们创建一个标准的 Spring MVC 控制器,在内部注入 SOAP 客户端,完成“翻译”工作。
package com.example.restapi;
import com.example.soapclient.LegacySoapClient;
import com.example.soapclient.SoapUserResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/users")
public class UserRestAdapter {
private final LegacySoapClient legacySoapClient;
// 注入 SOAP 客户端
public UserRestAdapter(LegacySoapClient legacySoapClient) {
this.legacySoapClient = legacySoapClient;
}
/**
* REST 接口: GET /api/v1/users/{id}
* 内部逻辑: 接收 REST 请求 -> 调用 SOAP -> 转换数据 -> 返回 JSON
*/
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
// 1. [调用] 委托给 SOAP 客户端
SoapUserResponse soapResponse = legacySoapClient.getUserFromSoapService(id);
// 2. [转换] SOAP 响应对象 -> REST DTO
UserDTO userDTO = convertToDTO(soapResponse);
// 3. [响应] 返回 JSON
return ResponseEntity.ok(userDTO);
}
/**
* 数据映射方法:负责字段过滤和重命名
*/
private UserDTO convertToDTO(SoapUserResponse soapUser) {
UserDTO dto = new UserDTO();
dto.setId(soapUser.getUserId());
dto.setName(soapUser.getFullName());
// 故意忽略 emailAddress 字段,实现字段过滤
return dto;
}
}
⚙️ 第五步:配置类
为了让 WebServiceTemplate 能够处理 XML 和 Java 对象的转换,我们需要配置一个 Marshaller。
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.client.core.WebServiceTemplate;
@Configuration
public class SoapConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// 指定包含 JAXB 注解(@XmlRootElement)的包路径
marshaller.setPackagesToScan("com.example.soapclient");
return marshaller;
}
@Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate template = new WebServiceTemplate();
template.setMarshaller(marshaller);
template.setUnmarshaller(marshaller);
return template;
}
}
🧪 测试与验证
启动 Spring Boot 应用后,我们可以使用 Postman 或 curl 进行测试。
请求:
GET http://localhost:8080/api/v1/users/1001
控制台日志:
>>> 正在调用 SOAP 服务,获取用户 ID: 1001
响应结果 (JSON):
{
"id": "1001",
"name": "张三 (来自 SOAP)"
}
通过这种方式,我们成功实现了:
- 协议解耦:前端不再依赖复杂的 SOAP XML,而是使用轻量级的 JSON。
- 接口现代化:将 SOAP 的过程式调用(通常只有 POST)转换为了符合 REST 风格的资源操作(GET /users/{id})。
- 数据脱敏:在
convertToDTO方法中,我们可以灵活地隐藏 SOAP 返回的敏感字段(如 email),只暴露必要信息。
这种“适配器模式”是遗留系统重构中最稳妥、最常用的手段之一。