SOAP协议在早期的企业级系统中应用非常广泛,银行、电信、政务等领域的老系统至今仍有大量接口基于SOAP暴露。对接这类接口时,如果手动拼接XML报文、处理命名空间和序列化问题,工作量巨大且极易出错。好在几乎所有主流语言都提供了根据WSDL自动生成客户端代码的工具,只需一条命令就能把接口的调用代码、请求响应实体类全部生成出来。本文以Java技术栈为主,讲解几种最常用的SOAP客户端代码生成方式。

一、使用JDK自带的wsimport工具生成客户端代码
wsimport是JDK自带的命令行工具(JDK 8及之前版本随JDK发布,位于JDK_HOME\bin目录下,JDK 11之后被移除),它可以根据WSDL地址生成调用SOAP服务所需的全部Java代码,包括服务代理类、请求响应对象以及JAXB注解的实体类。这是最简单直接的方式,不需要引入任何第三方依赖。
基本用法很简单,打开命令行执行以下命令即可:
wsimport -keep -p com.example.client -d D:\wsdl\output http://service.ippipp.com/UserService?wsdl
几个关键参数的含义需要理解清楚:-keep表示保留生成的源文件(默认只生成class文件);-p指定生成代码的包名,不指定时会根据WSDL中的namespace自动推导,往往推导出的包名很不友好;-d指定输出目录;最后是WSDL的地址,既可以是远程URL,也可以是本地的wsdl文件路径。
生成完成后,调用方式通常如下:
// 生成的服务类会提供getxxxPort方法获取代理对象 UserService service = new UserService(); UserPort port = service.getUserServicePort(); // 像调用本地方法一样调用远程接口 String result = port.getUserName(1001L); System.out.println(result);
需要注意一个常见的坑:如果WSDL中定义的schema引用了外部XSD文件,而服务器对该XSD设置了访问限制,wsimport会报解析失败的错误。解决办法是先把WSDL和所有依赖的XSD下载到本地同一目录,用本地文件路径生成,同时可能需要手动修改WSDL中schemaLocation指向本地文件。
二、使用Apache CXF的wsdl2java生成代码
当项目本身使用Apache CXF框架,或者需要生成更规范的代码结构时,wsdl2java是更好的选择。它生成的代码天然与CXF框架兼容,并且支持更多控制参数。使用前需要先去CXF官网下载发行包,解压后配置两个环境变量:CXF_HOME指向解压目录,并把%CXF_HOME%\bin追加到Path中。
配置完成后,命令行验证:
wsdl2java -version
生成代码的典型命令如下:
wsdl2java -d src -p com.example.cxf.client -client -encoding UTF-8 http://service.ippipp.com/UserService?wsdl
其中-d指定源码输出目录,-p指定包名,-client会额外生成一个包含main方法的客户端示例类,可以直接运行测试接口连通性,-encoding指定编码避免中文乱码。相比wsimport,wsdl2java生成的代码风格更统一,注释更完整,而且对WSDL 2.0、WS-Security等规范的支持也更完善。
wsdl2java调用接口的代码风格略有不同,它通过JaxWsProxyFactoryBean或生成的Service类获取代理。如果服务端要求认证,还可以在客户端配置拦截器添加SOAP Header,这是wsimport生成的裸代码做不到的:
UserPort port = new UserService().getUserServicePort(); // 通过BindingProvider设置HTTP Basic认证 Map<String, Object> ctx = ((BindingProvider) port).getRequestContext(); ctx.put(BindingProvider.USERNAME_PROPERTY, "admin"); ctx.put(BindingProvider.PASSWORD_PROPERTY, "password123"); String result = port.getUserName(1001L);
三、使用Maven插件在构建阶段自动生成
手动执行命令行生成代码有一个明显缺点:WSDL更新后需要重新手动生成并提交代码,团队协作时容易遗漏。更工程化的做法是把代码生成集成到Maven构建生命周期中,每次构建自动从WSDL生成代码。常用的是cxf-codegen-plugin插件。
在pom.xml中添加如下配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>3.5.5</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/UserService.wsdl</wsdl>
<packagenames>
<packagename>com.example.client</packagename>
</packagenames>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行mvn generate-sources后,插件会把生成的代码放到target/generated-sources/cxf目录并自动加入编译路径。这种方式的最佳实践是把WSDL文件保存到项目的src/main/resources/wsdl目录下,而不是每次从远程地址拉取。这样做的好处一是构建不依赖远程服务的可用性,二是版本可控,服务端接口变更时可以清晰对比WSDL差异。
另外提一句,如果项目是Spring Boot工程,还可以在生成代码的基础上,把Port代理对象注册为Spring Bean,配合@Autowired注入使用,整体代码风格会与项目保持一致。还可以通过配置类统一设置超时时间、重试策略,避免在业务代码中散落各种配置。
四、常见问题与注意事项
实际使用中经常会遇到几个典型问题。第一是字符编码问题,如果WSDL或XSD中包含中文,生成时务必指定UTF-8编码,否则中文注释和字段名会乱码。第二是命名冲突,当WSDL中存在同名类型时,wsimport会直接报错,可以通过-B-XautoNameResolutions参数让JAXB自动重命名冲突类型。
第三是JDK版本兼容问题。JDK 11之后移除了JAX-WS相关的模块,wsimport命令不复存在,此时必须改用CXF的wsdl2java,或者在项目中显式引入jakarta.xml.ws-api等依赖。第四是WSDL地址变更问题,生成代码时会把WSDL地址硬编码进Service类,生产环境地址往往不同,可以通过((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, newUrl)在运行时动态切换地址。
最后建议在生成代码后不要直接修改生成文件,因为下次重新生成会覆盖改动。如果需要定制行为,比如添加拦截器、修改超时,应该在调用层封装处理。把生成代码当作只读的依赖来对待,才是维护这类项目的正确姿势。
SOAP客户端wsimportcxf wsdl2java修改时间:2026-09-06 19:16:37