最简单的SpringBoot对接WebService接口,解析DataSet类型

1,415 阅读4分钟

这几天在研究对接 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>