这几天在研究对接 ERP 接口的事情,尽管现在还没到那一步,但是未雨绸缪,先了解一下 WebService 的接口怎么对接,怎么最方便的对接。声明:这是客户端调用服务端的实践,没有做服务端的 webservice 接口发布!
说实话,有点头疼。开始以为就是调用而已,到时候直接网上找找就行了,但是深入实践下来发现并没有这么简单,主要头疼点在于:
- 调用麻烦,网上五花八门的解决方案,又是 IDEA 插件,又是框架,又是依赖,甚至还要使用命令行生成代码,不知道是不是自己老了,没这个精力去研究,试了几次就不想弄了,实在麻烦
- 生成的 XML 如何解析,同样的,网上还是各种五花八门的方案。说实话,对于结构简单一目了然的数据类型是可以解决,但是对于 DataSet 这种,并没有很好的解决方案,偶尔能找到一两个,但是也是比较简单的案例。这么多案例里面居然就找不到一个使用最广泛的天气案例的 webservice 中
getSupportDataSet这个方法的调用和解析
天气案例的 webservice 接口
www.webxml.com.cn/WebServices…
解决思路
自己针对于这两个问题的解决,自认为是比较方便和简单的,先说下思路,再给出具体实践案例。
调用 webservice 的思路
网上有各种解决方式, CXF AXIS 等等。但是都比较复杂,经过实践,我发现使用 http的请求模拟 soap 请求是最方便的,不需要引入额外依赖,并且通俗易懂,容易理解
XML 解析的思路
调用以后返回 xml 格式的 String,然后使用 jdom2逐层遍历,不管结构多复杂,找出你想要的元素就行,然后解析的过程中用 JAVA 实体承载就行了。还有其他解析 XML 节点的方式,但是个人感觉 jdom2的 API 是最通俗易懂的
实践
工具准备:SOAPUI
网上有很多使用教程,但是这里使用 SOAPUI 仅是为了查看 SOAP 请求报文,用来在代码中组成一样的请求报文。这里以天气服务为案例,调用两个方法。
只写主要代码,Controller就不写了
getSupportCity
首先使用 SOAPUI请求该接口(具体教程网上搜,就是导入 wsdl,然后请求,很简单)
红框内的就是 soap 请求报文和 SOAPAction 只需要在代码中拼接出这样的请求就行
public void getSupportCity2(String province) throws SOAPException {
//构造soap请求参数
StringBuffer soapRequestData = new StringBuffer();
soapRequestData.append("<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://WebXml.com.cn/">\n");
soapRequestData.append("<soap:Header/>\n");
soapRequestData.append("<soap:Body>\n");
soapRequestData.append("<web:getSupportCity>\n");
soapRequestData.append("<web:byProvinceName>").append(province).append("</web:byProvinceName>\n");
soapRequestData.append("</web:getSupportCity>\n");
soapRequestData.append("</soap:Body>\n");
soapRequestData.append("</soap:Envelope>");
String soapAction = "http://WebXml.com.cn/getSupportCity";
String result = SoapHttpRequestUtil.doPostSoap(serviceUrl, soapRequestData.toString(), soapAction);
System.out.println(result);
}
打印结果
SoapHttpRequestUtil
@Slf4j
@Component
public class SoapHttpRequestUtil {
/**
* 使用SOAP发送消息
*
* @param serviceUrl webservice地址(asmx的地址拼接?wsdl就行)
* @param soapXml 拼接的xml请求报文
* @return 返回报文
*/
public static String doPostSoap(String serviceUrl, String soapXml, String soapAction) {
HttpHeaders httpHeaders = new HttpHeaders();
MediaType type = MediaType.parseMediaType("text/xml;charset=UTF-8");
httpHeaders.setContentType(type);
httpHeaders.add("SOAPAction", soapAction);
RestTemplate restTemplate = SpringUtils.getBean("restTemplate");
HttpEntity<String> formEntity = new HttpEntity<>(soapXml, httpHeaders);
return restTemplate.postForObject(serviceUrl, formEntity, String.class);
}
}
获取数据之后就是对 XML进行解析,这里我封装了一个方法,用于从整个 xml 中获取需要的节点。注意:需要你对返回的 XML 结构很熟悉,可以在 SOAPUI 中查看
public class XMLUtil {
/**
* 获取目标元素
* @param firstElement 根元素
* @param yourElementName 目标元素名称
* @return 目标元素
*/
public static Element getTargetElement(Element firstElement, String yourElementName) {
Element targetElement = null;
if(!firstElement.getName().equals(yourElementName)){
List<Element> children = firstElement.getChildren();
for(Element child :children) {
if(!child.getName().equals(yourElementName)) {
targetElement = getTargetElement(child, yourElementName);
} else {
targetElement = child;
return targetElement;
}
}
}
return targetElement;
}
}
放到案例中使用
public List<String> getSupportCity2(String province) throws SOAPException {
//构造soap请求参数
StringBuffer soapRequestData = new StringBuffer();
soapRequestData.append("<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://WebXml.com.cn/">\n");
soapRequestData.append("<soap:Header/>\n");
soapRequestData.append("<soap:Body>\n");
soapRequestData.append("<web:getSupportCity>\n");
soapRequestData.append("<web:byProvinceName>").append(province).append("</web:byProvinceName>\n");
soapRequestData.append("</web:getSupportCity>\n");
soapRequestData.append("</soap:Body>\n");
soapRequestData.append("</soap:Envelope>");
String soapAction = "http://WebXml.com.cn/getSupportCity";
String result = SoapHttpRequestUtil.doPostSoap(serviceUrl, soapRequestData.toString(), soapAction);
List<String> cities = Lists.newArrayList();
try {
Element root = getRootElement(result);
Element getSupportCityResult = XMLUtil.getTargetElement(root, "getSupportCityResult");
for(Element child : getSupportCityResult.getChildren()) {
cities.add(child.getValue());
}
System.out.println(getSupportCityResult);
} catch (Exception e) {
throw new RuntimeException(e);
}
return cities;
}
private Element getRootElement(String xml) throws IOException, JDOMException {
SAXBuilder saxBuilder = new SAXBuilder();
InputStream in = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
Document doc = saxBuilder.build(in);
return doc.getRootElement();
}
最后的返回结果
getSupportDataSet
上面已经分析了怎么使用,所以直接贴代码
public NewDataSet getSupportDataSet() {
//构造soap请求参数,可以用soapUI查看
StringBuffer soapRequestData = new StringBuffer();
soapRequestData.append("<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://WebXml.com.cn/">\n");
soapRequestData.append("<soap:Header/>\n");
soapRequestData.append("<soap:Body>\n");
soapRequestData.append("<web:getSupportDataSet/>\n");
soapRequestData.append("</soap:Body>\n");
soapRequestData.append("</soap:Envelope>\n");
String soapAction = "http://WebXml.com.cn/getSupportDataSet";
String result = SoapHttpRequestUtil.doPostSoap(serviceUrl, soapRequestData.toString(),soapAction);
try {
Element root = getRootElement(result);
Element newDataSetElement = XMLUtil.getTargetElement(root, "NewDataSet");
return getNewDataSet(newDataSetElement);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static NewDataSet getNewDataSet(Element newDataSetElement) {
List<Zone> zones = Lists.newArrayList();
List<Area> areas = Lists.newArrayList();
for(Element element : newDataSetElement.getChildren()) {
//Zone类
if(element.getName().equals("Zone")){
Zone zoneEntity = new Zone();
for(Element zone : element.getChildren()) {
if(zone.getName().equals("ID")) {
zoneEntity.setId(Integer.parseInt(zone.getValue()));
}else if(zone.getName().equals("Zone")) {
zoneEntity.setZone(zone.getValue());
}
}
zones.add(zoneEntity);
}else if (element.getName().equals("Area")) {
Area areaEntity = new Area();
for(Element area : element.getChildren()) {
if(area.getName().equals("ID")) {
areaEntity.setId(Integer.parseInt(area.getValue()));
}else if(area.getName().equals("Area")) {
areaEntity.setArea(area.getValue());
}else if(area.getName().equals("ZoneID")) {
areaEntity.setZoneID(Integer.parseInt(area.getValue()));
}else if(area.getName().equals("AreaCode")) {
areaEntity.setAreaCode(area.getValue());
}
}
areas.add(areaEntity);
}
}
NewDataSet newDataSet = new NewDataSet();
newDataSet.setAreas(areas);
newDataSet.setZones(zones);
return newDataSet;
}
最后的返回结果
最后差点忘了,整个过程中只使用了 jdom2的依赖
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
</dependency>