使用WSDL生成Java WebService客户端及SoapUI测试与字符编码问题解析
概述
本文介绍如何基于WSDL文件生成Java WebService客户端代码,利用SoapUI进行可视化接口测试,并深入分析调用过程中常见的中文乱码问题及其解决方案。
环境准备与工具说明
在Java开发中,JDK自带的wsimport和wsgen是处理WebService的重要命令行工具:
- wsimport:根据WSDL描述文件生成对应的Java客户端类。
- wsgen:从服务实现类反向生成WSDL及相关绑定文件。
这两个工具位于%JAVA_HOME%/bin目录下,无需额外依赖即可使用。
通过WSDL生成Java客户端代码
执行以下命令可自动生成客户端代码:
wsimport -encoding utf8 -p com.example.client -wsdllocation http://example.com/service.wsdl http://example.com/service.wsdl
-encoding utf8:指定生成的Java源文件编码格式为UTF-8。-p com.example.client:设置生成类的包名。-wsdllocation:指定运行时加载的WSDL地址(用于动态定位)。- 最后一个参数为本地或远程WSDL文件路径。
关键生成类解析
生成的代码通常包含三类核心元素:
1. 服务入口类(@WebServiceClient)
该类继承javax.xml.ws.Service,对应WSDL中的<service>节点。例如:
@WebServiceClient(name = "PatientService", targetNamespace = "http://healthcare.system", wsdlLocation = "http://his.example.com/ws/PatientService.wsdl")
public class PatientService extends Service {
private final static URL WSDL_LOCATION;
static {
URL url = null;
try {
url = new URL("http://his.example.com/ws/PatientService.wsdl");
} catch (MalformedURLException e) {
throw new WebServiceException(e);
}
WSDL_LOCATION = url;
}
public PatientService() {
super(WSDL_LOCATION, new QName("http://healthcare.system", "PatientService"));
}
public PatientPortType getPatientPort() {
return super.getPort(new QName("http://healthcare.system", "PatientPort"), PatientPortType.class);
}
}
2. 接口定义类(@WebService)
此接口对应WSDL中的<portType>,声明了可用的操作方法:
@WebService(name = "PatientPortType", targetNamespace = "http://healthcare.system")
@XmlSeeAlso({ObjectFactory.class})
public interface PatientPortType {
@WebMethod(operationName = "getPatientInfo")
@WebResult(name = "patientData")
String getPatientInfo(
@WebParam(name = "patientId") String patientId,
@WebParam(name = "hospitalCode") String hospitalCode
);
}
3. JAXB对象工厂(ObjectFactory)
用于XML与Java对象之间的序列化支持:
@XmlRegistry
public class ObjectFactory {}
客户端调用示例
使用生成的类发起请求非常直观:
PatientService service = new PatientService();
PatientPortType port = service.getPatientPort();
String result = port.getPatientInfo("P1001", "HOS_007");
System.out.println(result);
使用SoapUI进行可视化测试
SoapUI是一款功能强大的WebService测试工具,尤其适合调试复杂的SOAP服务。
创建SOAP项目
- 打开SoapUI开源版(Free Edition)。
- 选择"File" → "New SOAP Project"。
- 输入WSDL地址,如:
http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl - 点击确定后,SoapUI会自动解析所有可用操作。
查看服务结构
以天气服务为例,其WSDL中包含多个端口:
<wsdl:service name="WeatherWebService">
<wsdl:port name="WeatherWebServiceSoap" binding="tns:WeatherWebServiceSoap">
<soap:address location="http://www.webxml.com.cn/WebServices/WeatherWebService.asmx"/>
</wsdl:port>
<wsdl:port name="WeatherWebServiceSoap12" binding="tns:WeatherWebServiceSoap12">
<soap12:address location="http://www.webxml.com.cn/WebServices/WeatherWebService.asmx"/>
</wsdl:port>
</wsdl:service>
其中,SOAP 1.1 和 1.2 分别对应不同的协议版本。
发送请求并查看响应
- 双击某个操作(如
getSupportCity),进入请求编辑界面。 - 填写输入参数,点击绿色运行按钮执行。
- 右侧面板显示完整的HTTP通信内容。
底层通信原理
尽管WebService基于SOAP协议,但底层仍使用HTTP传输。请求体遵循标准SOAP封装:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:web="http://WebXml.com.cn/">
<soapenv:Header/>
<soapenv:Body>
<web:getSupportCity>
<web:byProvinceName>广东</web:byProvinceName>
</web:getSupportCity>
</soapenv:Body>
</soapenv:Envelope>
响应同样封装在Envelope中:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getSupportCityResponse xmlns="http://WebXml.com.cn/">
<getSupportCityResult>
<string>广州</string>
<string>深圳</string>
</getSupportCityResult>
</getSupportCityResponse>
</soap:Body>
</soap:Envelope>
解决中文乱码问题
在调用由ASP.NET提供的WebService时,常出现中文乱码而英文数字正常的现象。根本原因在于:
- 服务器返回的实际字节流为UTF-8编码。
- JVM默认使用平台编码(如ISO-8859-1)解析字节流,导致汉字被错误解码为乱码字符。
乱码复现与分析
模拟错误解码过程:
byte[] utf8Bytes = "你好啊".getBytes(StandardCharsets.UTF_8);
String wrongStr = new String(utf8Bytes, StandardCharsets.ISO_8859_1); // 错误解码
System.out.println(wrongStr); // 输出类似 "浣犲ソ鍟"
// 恢复原始字节并正确解码
byte[] recovered = wrongStr.getBytes(StandardCharsets.ISO_8859_1);
String correctStr = new String(recovered, StandardCharsets.UTF_8);
System.out.println(correctStr); // 正确输出 "你好啊"
通用修复方法
封装一个工具函数用于纠正编码:
/**
* 将因错误解码产生的乱码字符串还原为原始UTF-8文本
* @param corruptedStr 已经乱码的字符串
* @return 正确解码后的字符串
*/
public static String fixEncoding(String corruptedStr) {
if (corruptedStr == null) return null;
byte[] bytes = corruptedStr.getBytes(StandardCharsets.ISO_8859_1);
return new String(bytes, StandardCharsets.UTF_8);
}
深层原因探究
当使用GBK尝试解码UTF-8字节流时,由于编码单位不一致(UTF-8三字节表示一个汉字,GBK两字节),部分无法识别的字节会被替换为?(ASCII码3F)。一旦字节流被修改,即使后续重新编码也无法恢复原貌。
示例:
"一二三" UTF-8编码:E4 B8 80 E4 BA 8C E4 B8 89
误用GBK解码后:涓浜屼笁 (中间字节变为3F)
再转回UTF-8:?二三 —— 数据已永久损坏
总结
通过wsimport生成客户端是集成传统WebService系统的高效方式,避免手动构建SOAP消息的复杂性。配合SoapUI可以快速验证接口行为。对于跨语言系统集成中的编码问题,需明确传输层的真实编码格式,并在必要时进行字节级修复。建议在系统对接初期即确认双方的字符编码规范,防止后期数据污染。