虽然现在 RESTful 接口已经成为主流,但在银行、电信、政务等传统行业里,SOAP 协议依然占据着大量存量接口。很多新项目基于 Spring Boot 构建,往往不可避免地要和这些 SOAP 服务打交道,要么作为服务提供方对外发布接口,要么作为客户端去调用第三方系统。本文以 Apache CXF 为例,完整演示 Spring Boot 环境下 SOAP 服务的发布与调用全过程。

一、为什么选择 Apache CXF
在 Java 生态中实现 SOAP 的框架主要有两类:一类是 JAX-WS 规范的参考实现 Metro,另一类就是 Apache CXF。CXF 是目前社区最活跃、与 Spring 整合最成熟的方案,它同时支持 JAX-WS 和 JAX-RS 两种编程模型,可以和 Spring Boot 无缝集成。
相比之下,直接使用 JDK 自带的 JaxWsServerFactoryBean 发布服务虽然零依赖,但功能过于简陋,不支持复杂的 WS-Security、拦截器链管理也不方便。而 CXF 提供了完善的拦截器机制,可以轻松实现日志打印、权限校验、报文加密等横切需求,这对生产环境来说非常关键。
另外需要说明一点,很多同学容易把 CXF 和 Axis2 搞混。Axis2 已经基本停止维护,新项目不建议再选型,除非你要对接的老系统指定要求。
二、服务端:发布一个 SOAP 服务
1. 引入依赖
在 pom.xml 中加入 CXF 的 Spring Boot Starter,注意版本号建议使用 3.4.x 以上的稳定版本:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.4.5</version>
</dependency>2. 编写服务接口与实现类
SOAP 服务基于契约,代码优先的方式下只需要在接口上标注 @WebService 注解即可。先定义一个简单的用户查询接口:
import javax.jws.WebService;
@WebService(name = "UserService",
targetNamespace = "http://service.demo.ipipp.com/")
public interface UserService {
String getUserName(Long userId);
}实现类同样需要标注 @WebService,并且要指定 endpointInterface 指向接口的全限定名,同时把类交给 Spring 容器管理,加上 @Service 注解:
import org.springframework.stereotype.Service;
import javax.jws.WebService;
@Service
@WebService(serviceName = "UserService",
targetNamespace = "http://service.demo.ipipp.com/",
endpointInterface = "com.ipipp.demo.service.UserService")
public class UserServiceImpl implements UserService {
@Override
public String getUserName(Long userId) {
return "用户_" + userId;
}
}3. 发布 CXF 端点
接下来通过配置类把服务发布出去。CXF 提供了 SpringBus 和 Endpoint 相关 API,配合 CXFServlet 的默认路径 /services 即可完成发布:
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.xml.ws.Endpoint;
@Configuration
public class CxfConfig {
@Bean
public Endpoint userServiceEndpoint(Bus bus, UserService userService) {
EndpointImpl endpoint = new EndpointImpl(bus, userService);
endpoint.publish("/userService");
return endpoint;
}
}启动应用后访问 http://127.0.0.1:8080/services/userService?wsdl,如果能看到 WSDL 文档,说明服务已经发布成功。这里有个小细节:如果应用配置了 server.servlet.context-path,访问地址前面还要拼上上下文路径。
三、客户端:调用 SOAP 服务
客户端调用推荐使用 CXF 的动态代理方式,不需要先生成客户端代码,直接通过 JaxWsProxyFactoryBean 创建代理对象即可:
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CxfClientConfig {
@Bean
public UserService userServiceClient() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(UserService.class);
factory.setAddress("http://127.0.0.1:8080/services/userService");
return (UserService) factory.create();
}
}然后在业务代码中直接注入使用就行。如果需要查看请求和响应的原始报文,可以给工厂加上日志拦截器:
factory.getInInterceptors().add(new LoggingInInterceptor()); factory.getOutInterceptors().add(new LoggingOutInterceptor());
对于契约优先的场景,也就是对方只给你一份 WSDL 文件时,可以先用 CXF 自带的 wsdl2java 命令生成客户端代码,把生成结果拷贝到工程里再调用。命令如下:
wsdl2java -d src/main/java -p com.ipipp.demo.client http://127.0.0.1:8080/services/userService?wsdl
四、常见问题排查
第一类常见问题是访问 WSDL 报 404。这种情况多半是端点发布路径写错,或者引入了其他会注册 /services 路径的组件产生冲突。可以通过配置项 cxf.path=/soap 把 CXF 的根路径改掉来规避冲突。
第二类是调用时报命名空间不匹配的错误,典型异常信息是 Unexpected wrapper element。SOAP 对命名空间非常敏感,客户端与服务端的 targetNamespace 必须完全一致,包括大小写和末尾的斜杠,建议统一约定为接口所在包名的倒序写法,由 CXF 自动生成,避免手写出错。
第三类是返回对象包含 List、Map 等复杂结构时序列化失败。SOAP 默认只能很好地处理 POJO 和数组,Map 需要借助 @XmlJavaTypeAdapter 做适配。实践中更推荐的做法是定义专门的 DTO 对象,把集合包装在对象内部,生成的 WSDL 结构也更清晰,对接方理解起来更容易。
最后提醒一下,JDK 9 之后 javax.jws 相关包被移出了标准库,如果项目使用 JDK 11 及以上版本,需要额外引入 jakarta.xml.ws-api 和 javax.annotation-api 依赖,否则编译会直接报找不到注解的错误,这是升级 JDK 时最容易踩的坑。
五、小结
整体来看,Spring Boot 整合 SOAP 的核心就三步:引入 CXF Starter、编写带 @WebService 注解的接口与实现、通过配置类发布端点。客户端侧使用动态代理可以做到极简调用。虽然 SOAP 显得有些笨重,但配合 CXF 的拦截器体系,日志、鉴权、加签等功能都可以优雅地插拔实现。掌握这套流程后,无论是对接传统企业系统还是迁移存量接口,都能从容应对。
Spring Boot SOAPApache CXFWebService修改时间:2026-09-05 21:32:45