背景

笔者当前从事制造业相关行业软件工作,因工作需要,在MES系统中需要向SAP拉取订单、物料、工序等数据。

笔者接触的SAP提供了跨系统通信的Po中间件(实际上是WebService SOAP1.0)。

 

通常情况下,我们只要使用wsdl生成工具,生成本地的调用客户端即可。常用的客户端工具是apace-cxf。

IDEA旗舰版本中,邮件项目,有个WebService相关的菜单,输入wsdl地址即可。社区版一般安装一个maven插件,如下

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-codegen-plugin</artifactId>
            <version>3.4.5</version>
            <executions>
                <execution>
                    <id>generate-sources-w2j</id>
                    <phase>generate-sources</phase>
                    <configuration>
                        <sourceRoot>src/main/java</sourceRoot>
                        <defaultOptions>
                            <extraargs>
                                <extraarg>-impl</extraarg>
                                <extraarg>-verbose</extraarg>
                                <extraarg>-validate</extraarg>
                                <!--<extraarg>-client</extraarg>-->
                            </extraargs>
                        </defaultOptions>
                        <wsdlOptions>
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/2f1826f53a9b398a83000fdd7a05814e</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!-- MES向SAP 按时间读取物料主数据接口-->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/fb71dee0a5c538d99d305c6f28eebd53</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!-- MES向SAP 生产报工同步接口 -->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/46d3848617fa3dd7bc806f248d51340f</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!-- MES向SAP 按物料编号读取物料主数据接口 -->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/48c28c49f4fc337d9fdfc3d5feee4e66</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!--派工单下达传输-->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/6dcd80497ea937c8b484635b554710f4</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!--工单开工标识-->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/4c602e2c4d673d3ba71a205d708b9c02</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                            <!-- MES向SAP 工单信息读取 -->
                            <wsdlOption>
                                <wsdl>http://www1.host.com/dir/wsdl?p=ic/40dd063793613e2caec3d96510577b37</wsdl>
                                <extendedSoapHeaders>true</extendedSoapHeaders>
                                <autoNameResolution>true</autoNameResolution>
                            </wsdlOption>
                        </wsdlOptions>
                    </configuration>
                    <goals>
                        <goal>wsdl2java</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</plugins>

然后在右侧点击wsdl2java即可生成本地客户端调用代码

 

 

 这是一是常规情况,SAP的特殊支持就是在通信协议层面做了Http Basic认证,即在请求头要添加

Authorization: Basic base64字符串

以下是物料按时间拉取的一个标准请求报文及相应报文,使用soapui发包,通过wireshark抓取

POST /XISOAPAdapter/MessageServlet?senderParty=&senderService=BS_MES_DEV&receiverParty=&receiverService=
&interface=SI_MaterialMasterData_In&interfaceNamespace=http://host.com/MES/MaterialMasterData/Sender HTTP/1.1 Accept-Encoding: gzip,deflate Content-Type: text/xml;charset=UTF-8 SOAPAction: "http://sap.com/xi/WebService/soap1.1" Content-Length: 450 Host: www1.host.com Connection: Keep-Alive User-Agent: Apache-HttpClient/4.5.5 (Java/15) Authorization: Basic VmlzaXRvcjpxd2VyMTIzNA== <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:sap-com:document:sap:rfc:functions"> <soapenv:Header/> <soapenv:Body> <urn:ZMES_SAP_003> <!--You may enter the following 4 items in any order--> <!--Optional:--> <BEDAT>20211210</BEDAT> <!--Optional:--> <EDDAT>20211212</EDDAT> </urn:ZMES_SAP_003> </soapenv:Body> </soapenv:Envelope>HTTP/1.1 200 OK server: SAP NetWeaver Application Server 7.49 / AS Java 7.50 date: Thu, 23 Dec 2021 07:41:51 GMT set-cookie: MYSAPSSO2=AjExMDAgAA5wb3J0YWw6dmlzaXRvcogAB2RlZmF1bHQBAAdWSVNJVE9SAgADMDAwAwADUE9EBAAMMjAyMTEyMjMwNz
QxBQAEAAAACAoAB1ZJU0lUT1L%2FAQYwggECBgkqhkiG9w0BBwKggfQwgfECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHATGB0TCBzgIBAT
AiMB0xDDAKBgNVBAMTA1BPRDENMAsGA1UECxMESjJFRQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG
9w0BCQUxDxcNMjExMjIzMDc0MTUxWjAjBgkqhkiG9w0BCQQxFgQUdWxoAzhEYiSIc27jSe8FsKCEbjswCQYHKoZIzjgEAwQwMC4CFQDLMD
BWN7xZHY!5EWvCn3aesvvO6wIVAJWRZf5SLYj6KJlD!DaUx%2F!D3X!H;path=/;domain=.host.com;HttpOnly content-type: text/xml; charset=utf-8 content-id:
<soap-cb1e8be463c311ec92be0000002094d2@sap.com> content-disposition: attachment;filename="soap-cb1e8be463c311ec92be0000002094d2@sap.com.xml" content-description: SOAP content-encoding: gzip content-length: 557 set-cookie: saplb_*=(J2EE2135220)2135250; Version=1; Path=/ set-cookie: JSESSIONID=lvNdixDYPSn8vdeDR9UxlrxfOT3mfQHSlCAA_SAPZmasLjjtl7ew9y4UdoHLQTn2; Version=1; Path=/ set-cookie: JSESSIONMARKID=9EvTHA-TE05LwidImrSEHzSGbAroysc13JPdKUIAA; Version=1; Path=/ <SOAP:Envelope xmlns:SOAP='http://schemas.xmlsoap.org/soap/envelope/'><SOAP:Header/><SOAP:Body>
<
ns0:ZMES_SAP_003.Response xmlns:ns0='urn:sap-com:document:sap:rfc:functions'><MESSAGE>查询成功</MESSAGE>
<
STA>S</STA><IT_MARA><item><MATNR>A06010475</MATNR><MAKTX>卡扣 φ6.3-φ7 白</MAKTX><MTART>Z001</MTART>
<
MEINS>ST</MEINS><MATKL>3001</MATKL><GROES/><BISMT/><PRDHA/><MFRNR>0000200021</MFRNR><MFRPN/>
<
ZEXTRA1/><ZEXTRA2>C</ZEXTRA2><ZEXTRA3>18</ZEXTRA3><ZEXTRA4>总装领料</ZEXTRA4><ZEXTRA5>0</ZEXTRA5>
<
ZEXTRA6/><ZEXTRA7/><ZEXTRA8>180</ZEXTRA8><ZEXTRA9/><ZEXTRA10/><ZEXTRA13/></item></IT_MARA><IT_MARC>
<
item><MATNR>A06010475</MATNR><WERKS>1201</WERKS><EKGRP/><BSTMI>0</BSTMI><BESKZ/><SOBSL/><RGEKM/>
<
DZEIT>0</DZEIT><PLIFZ>0</PLIFZ><EISBE>0</EISBE><MAABC/><FEVOR/><BSTRF>0</BSTRF></item><item>
<
MATNR>A06010475</MATNR><WERKS>1101</WERKS><EKGRP>101</EKGRP><BSTMI>0</BSTMI><BESKZ>F</BESKZ>
<
SOBSL/><RGEKM/><DZEIT>0</DZEIT><PLIFZ>5</PLIFZ><EISBE>0</EISBE><MAABC/><FEVOR/><BSTRF>12000.000</BSTRF>
</
item><item><MATNR>A06010475</MATNR><WERKS>1001</WERKS><EKGRP>101</EKGRP><BSTMI>1000.000</BSTMI>
<
BESKZ>F</BESKZ><SOBSL/><RGEKM/><DZEIT>0</DZEIT><PLIFZ>30</PLIFZ><EISBE>0</EISBE><MAABC/><FEVOR/>
<
BSTRF>1000.000</BSTRF></item></IT_MARC></ns0:ZMES_SAP_003.Response></SOAP:Body></SOAP:Envelope>

常规情况下apache cxf生成的客户端是不能带自定义请求头的,payload可以是自定义。当然网上有注册一个bean,

代理cxf本地请求客户端,带入身份验证,笔者认为笔记麻烦,没去测试。

解决方案

这篇文章九不科普太多了,直接上解决方案

SOAPClient.java
import cn.hutool.core.annotation.AnnotationUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import com.xxx.mes.common.exception.SAPCommunicationException;
import com.xxx.mes.webservice.XPathExpression;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.dom4j.xpath.DefaultXPath;
import org.springframework.http.HttpStatus;

import javax.xml.bind.annotation.XmlRootElement;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Base64;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import static com.xxx.mes.common.util.XmlUtil.JaxbBinder.fromXml;

/***
 * SOAP客户端<br/>
 * 可以发送报文,解析报文映射成实体类
 *
 */
@Slf4j
public final class SOAPClient {

    private static final SAXReader saxReader = new SAXReader();

    private static final Map<Class<?>,String> annotatedClassCache = new LinkedHashMap<Class<?>,String>();

    /***
     * 从基于soap的webservice报文中提取响应结果
     * @param xml 返回的原始报文
     * @param expression xpath评估表达式
     * @param targetClass 待返回的结果
     * @param <T>
     * @return 如果解析成功将返回一个<code>T</code>类的响应实体
     * @throws DocumentException
     */
    public static <T> T extractResponse(String xml, XPathExpression expression, Class<T> targetClass) throws Exception {

        Document doc = saxReader.read(new ByteArrayInputStream(xml.getBytes()));
        DefaultXPath xpath = new DefaultXPath(expression.getExpression());
        xpath.setNamespaceURIs(Collections.singletonMap(expression.getNamespaceURI(), expression.getTargetNamespace()));
        Node node = xpath.selectSingleNode(doc);
        final T response = fromXml(targetClass, node.asXML());
        return response;
    }

    public static String getServiceUrl(String serviceName) {
        return String.format("http://www1.host.com/XISOAPAdapter/MessageServlet?senderParty=&senderService=BS_MES_DEV" +
                        "&receiverParty=&receiverService=&interface=SI_%s_In&interfaceNamespace=http://host.com/MES/%s/Sender"
                , new Object[]{serviceName, serviceName});
    }

    /***
     * 通过soap请求载荷构造soap请求报文
     * @param payload
     * @param <T>
     * @return xml-based soap request payload
     * @throws JsonProcessingException
     */
    public static <T> String buildSOAPRequest(T payload) throws JsonProcessingException {
        final Class<?> targetClass = payload.getClass();
        String elementName = annotatedClassCache.get(targetClass);


        if(elementName == null){
            elementName = AnnotationUtil.getAnnotation(targetClass, XmlRootElement.class).name();
            annotatedClassCache.put(targetClass,elementName);
        }

        XmlMapper xmlMapper = (XmlMapper) new XmlMapper()
                .setAnnotationIntrospector(new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()));

        //字段为null,自动忽略,不再序列化
        xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        StringBuilder builder = new StringBuilder();
        builder.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:sap-com:document:sap:rfc:functions\">\n" +
                "   <soapenv:Header/>\n" +
                "   <soapenv:Body>");
        builder.append("<urn:").append(elementName).append(">");
        String xml = xmlMapper.writeValueAsString(payload);
        builder.append(xml);
        builder.append("</urn:").append(elementName).append(">").
                append("   </soapenv:Body>").
                append("</soapenv:Envelope>");
        log.info("xml报文:{}", builder);
        return builder.toString().replace("<" + elementName + ">", "").replace("</" + elementName + ">", "");

    }

    /***
     * 向SAP服务器提供的Webservice发起调用请求
     * @param httpUrl 请求地址
     * @param soapBody 使用{@link #buildSOAPRequest(Object)}构造的请求报文
     * @return 返回xml格式的响应报文
     * @throws IOException
     */
    public static String send(String httpUrl, String soapBody) throws IOException {
        String resultData = "";
        URL geturl = null;
        try {
            // 构造一个URL对象
            geturl = new URL(httpUrl);
        } catch (MalformedURLException e) {
            throw new SAPCommunicationException("url地址格式无效:%s", new Object[]{httpUrl});
        }

        try {
            // 使用HttpURLConnection打开连接
            HttpURLConnection urlConn = (HttpURLConnection) geturl.openConnection();
            // 设置请求的超时时间
            urlConn.setReadTimeout(50000);
            urlConn.setConnectTimeout(50000);
            // Windows验证 用户密码
            String datap = "Visitor:qwer1234";

            String authorization = Base64.getEncoder().encodeToString(datap.getBytes());

            urlConn.setRequestProperty("Authorization", "Basic " + authorization);
            // 设置请求的头
            urlConn.setRequestProperty("Connection", "keep-alive");
            // 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
            urlConn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");

            urlConn.setRequestProperty("SOAPAction", "http://sap.com/xi/WebService/soap1.1");
            // 设置请求的头
            urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0");
            // 因为这个是post请求,设立需要设置为true
            urlConn.setDoOutput(true);
            urlConn.setDoInput(true);
            // 设置以POST方式
            urlConn.setRequestMethod("POST");

            // Post 请求不能使用缓存
            urlConn.setUseCaches(false);
            // 获取输出流
            // BufferedReader os = new BufferedReader(new
            // OutputStream(urlConn.getOutputStream()))
            OutputStream os = urlConn.getOutputStream();
            // byte[] content = data.getBytes("utf-8");
            os.write(soapBody.getBytes());
            os.flush();
            os.close();
            int statusCode = urlConn.getResponseCode();
            if (statusCode == HttpStatus.OK.value()) {
                // 获取响应的输入流对象
                InputStream is = urlConn.getInputStream();
                // 创建字节输出流对象
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 定义读取的长度
                int len;
                int irecord = 1;
                // 定义缓冲区
                byte buffer[] = new byte[1024];
                // 按照缓冲区的大小,循环读取
                while ((len = is.read(buffer)) != -1) {
                    // 根据读取的长度写入到os对象中
                    baos.write(buffer, 0, len);
                    irecord += 1;
                }
                // 释放资源
                is.close();
                baos.close();
                // 返回字符串
                resultData = new String(baos.toByteArray());
                irecord -= 1;
                return resultData;
            } else {
                urlConn.disconnect();
                throw new SAPCommunicationException("接口报文返回异常:http状态码,期望值200,实际:%d", new Object[]{statusCode});
            }
        } catch (Exception e) {
            log.error("SAP通信异常",e);
            throw e;
        }
    }

XPathExpression.java

import lombok.Data;

/**
 * 中间参数类,用于解析xml的xpath表达式<br/>
 * 数据样本:
 * <pre>
 *     <SOAP:Envelope
 *     xmlns:SOAP='http://schemas.xmlsoap.org/soap/envelope/'>
 *     <SOAP:Header/>
 *     <SOAP:Body>
 *         <ns0:ZMES_SAP_003.Response
 *             xmlns:ns0='urn:sap-com:document:sap:rfc:functions'>
 *             <MESSAGE>查询成功</MESSAGE>
 *             <STA>S</STA>
 *             <IT_MARA>
 *                 <item>
 *                     <MATNR>A06010475</MATNR>
 *                     <MAKTX>卡扣 φ6.3-φ7 白</MAKTX>
 *                     <MTART>Z001</MTART>
 *                     <MEINS>ST</MEINS>
 *                     <MATKL>3001</MATKL>
 *                     <GROES/>
 *                     <BISMT/>
 *                     <PRDHA/>
 *                     <MFRNR>0000200021</MFRNR>
 *                     <MFRPN/>
 *                     <ZEXTRA1/>
 *                     <ZEXTRA2>C</ZEXTRA2>
 *                     <ZEXTRA3>18</ZEXTRA3>
 *                     <ZEXTRA4>总装领料</ZEXTRA4>
 *                     <ZEXTRA5>0</ZEXTRA5>
 *                     <ZEXTRA6/>
 *                     <ZEXTRA7/>
 *                     <ZEXTRA8>180</ZEXTRA8>
 *                     <ZEXTRA9/>
 *                     <ZEXTRA10/>
 *                     <ZEXTRA13/>
 *                 </item>
 *             </IT_MARA>
 *             <IT_MARC>
 *                 <item>
 *                     <MATNR>A06010475</MATNR>
 *                     <WERKS>1201</WERKS>
 *                     <EKGRP/>
 *                     <BSTMI>0</BSTMI>
 *                     <BESKZ/>
 *                     <SOBSL/>
 *                     <RGEKM/>
 *                     <DZEIT>0</DZEIT>
 *                     <PLIFZ>0</PLIFZ>
 *                     <EISBE>0</EISBE>
 *                     <MAABC/>
 *                     <FEVOR/>
 *                     <BSTRF>0</BSTRF>
 *                 </item>
 *                 <item>
 *                     <MATNR>A06010475</MATNR>
 *                     <WERKS>1101</WERKS>
 *                     <EKGRP>101</EKGRP>
 *                     <BSTMI>0</BSTMI>
 *                     <BESKZ>F</BESKZ>
 *                     <SOBSL/>
 *                     <RGEKM/>
 *                     <DZEIT>0</DZEIT>
 *                     <PLIFZ>5</PLIFZ>
 *                     <EISBE>0</EISBE>
 *                     <MAABC/>
 *                     <FEVOR/>
 *                     <BSTRF>12000.000</BSTRF>
 *                 </item>
 *                 <item>
 *                     <MATNR>A06010475</MATNR>
 *                     <WERKS>1001</WERKS>
 *                     <EKGRP>101</EKGRP>
 *                     <BSTMI>1000.000</BSTMI>
 *                     <BESKZ>F</BESKZ>
 *                     <SOBSL/>
 *                     <RGEKM/>
 *                     <DZEIT>0</DZEIT>
 *                     <PLIFZ>30</PLIFZ>
 *                     <EISBE>0</EISBE>
 *                     <MAABC/>
 *                     <FEVOR/>
 *                     <BSTRF>1000.000</BSTRF>
 *                 </item>
 *             </IT_MARC>
 *         </ns0:ZMES_SAP_003.Response>
 *     </SOAP:Body>
 * </SOAP:Envelope>
 * </pre>
 *
 * @author: passedbylove
 * @date: Created by  2021/12/28 11:04
 * @version: 1.0.0
 */

@Data
public class XPathExpression {
    /***
     * xpath提取数据节点的表达式
     * eg.<br/>
     * //ns0:ZMES_SAP_003.Response
     */
    private String expression;
    /***
     * 命名空间的前缀
     * eg.<br/>
     * ns0
     */
    private String namespaceURI;
    /***
     * 命名空间
     * eg.<br/>
     * "urn:sap-com:document:sap:rfc:functions"
     */
    private String targetNamespace;


    public XPathExpression() {
    }

    public XPathExpression(String expression, String namespaceURI, String targetNamespace) {
        this.expression = expression;
        this.namespaceURI = namespaceURI;
        this.targetNamespace = targetNamespace;
    }
}
SAPCommunicationException.java
import lombok.NoArgsConstructor;

/**
 * 用于注明是SAP系统通信过程中产生的异常,区别于其他异常,比如调用SAP接口过程中可能产生
 * <code>UnknownHostException</</code>或者<code>IOException</code</code>等;此处归一化为<code>SAPCommunicationException</code>
 * 方便排错
 * @author: daiqiankun
 * @date: Created by  2021/12/28 11:11
 * @version: 1.0.0
 */
@NoArgsConstructor
public class SAPCommunicationException extends RuntimeException {

    public SAPCommunicationException(String message) {
        super(message);
    }

    public SAPCommunicationException(String format,Object[] args) {
        super(String.format(format,args));
    }

    public SAPCommunicationException(String message, Throwable ex) {
        super(message, ex);
    }

    public SAPCommunicationException(String message, Object[] args,Throwable ex) {
        super(String.format(message,args), ex);
    }
}
JaxbBinder.java
 /**
     * @datetime 2021/12/27 15:29
     */
    public static class JaxbBinder
    {
        /***
         * 缓存一下JAXBContext,减少反射开销
         */
        static Map<Class<?>, JAXBContext> contextCache = new LinkedHashMap<>();

        //多线程安全的Context.
        private JAXBContext jaxbContext;

        public JaxbBinder()
        {}

        /**
         * @param types 所有需要序列化的Root对象的类型.
         */
        public JaxbBinder(Class<?>... types) {
            try {
                jaxbContext = JAXBContext.newInstance(types);
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Java Object->Xml.
         */
        public String toXml(Object root, String encoding) {
            try {
                StringWriter writer = new StringWriter();
                createMarshaller(encoding).marshal(root, writer);
                return writer.toString();
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Java Object->Xml, 特别支持对Root Element是Collection的情形.
         */
        @SuppressWarnings("unchecked")
        public String toXml(Collection root, String rootName, String encoding) {
            try {
                CollectionWrapper wrapper = new CollectionWrapper();
                wrapper.collection = root;

                JAXBElement<CollectionWrapper> wrapperElement = new JAXBElement<CollectionWrapper>(new QName(rootName),
                        CollectionWrapper.class, wrapper);

                StringWriter writer = new StringWriter();
                createMarshaller(encoding).marshal(wrapperElement, writer);

                return writer.toString();
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Xml->Java Object.
         */
        @SuppressWarnings("unchecked")
        public <T> T fromXml(String xml) {
            try {
                StringReader reader = new StringReader(xml);
                return (T) createUnmarshaller().unmarshal(reader);
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 将XML转为指定的POJO
         *
         * @param clazz
         * @param xmlStr
         * @return
         * @throws Exception
         */
        public static <T> T fromXml(Class<?> clazz, String xmlStr) throws Exception {
            T xmlObject = null;
            Reader reader = null;

            JAXBContext context = null;
            context = contextCache.get(clazz);

            if (Objects.isNull(context)) {
                context = JAXBContext.newInstance(clazz);
                contextCache.put(clazz, context);
            }
            // XML 转为对象的接口
            Unmarshaller unmarshaller = context.createUnmarshaller();
            reader = new StringReader(xmlStr);
            //以文件流的方式传入这个string
            xmlObject = (T)unmarshaller.unmarshal(reader);
            if (null != reader) {
                reader.close();
            }
            return xmlObject;
        }

        /**
         * 创建Marshaller, 设定encoding(可为Null).
         */
        public Marshaller createMarshaller(String encoding) {
            try {
                Marshaller marshaller = jaxbContext.createMarshaller();

                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

                if (!StringUtils.isEmpty(encoding)) {
                    marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
                }
                return marshaller;
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 创建UnMarshaller.
         */
        public Unmarshaller createUnmarshaller() {
            try {
                return jaxbContext.createUnmarshaller();
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 封装Root Element 是 Collection的情况.
         */
        public static class CollectionWrapper {
            @SuppressWarnings("unchecked")
            @XmlAnyElement
            protected Collection collection;
        }


        @SuppressWarnings("unchecked")
        public <T> T fromXML(String fileName) {
            return (T)fromXML(new File(fileName));
        }


        @SuppressWarnings("unchecked")
        public <T> T fromXML(File file) {
            try {
                Unmarshaller unmarshaller = createUnmarshaller();
                return (T) unmarshaller.unmarshal(file);
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }


        @SuppressWarnings("unchecked")
        public <T> T fromXML(InputStream stream) {
            try {
                Unmarshaller unmarshaller = createUnmarshaller();
                return (T) unmarshaller.unmarshal(stream);
            } catch (JAXBException e) {
                throw new RuntimeException(e);
            }
        }
    }

调用方法

ZMESSAP003 zmessap003 = new ZMESSAP003();
zmessap003.setBEDAT("19901101");
zmessap003.setEDDAT("19901130");
final XmlRootElement annotation = AnnotationUtil.getAnnotation(ZMESSAP003.class, XmlRootElement.class);
final String data = buildSOAPRequest(zmessap003, annotation.name());
final String xml = SOAPClient.send("http://www1.host.com/XISOAPAdapter/MessageServlet?senderParty=&senderService=BS_MES_DEV&receiverParty=&receiverService=&interface=SI_MaterialMasterData_In&interfaceNamespace=http://xxx.com/MES/MaterialMasterData/Sender&sap-user=Visitor&sap-password=qwer1234", data);
System.out.println(data);
XPathExpression expression = new XPathExpression();
expression.setExpression("//ns0:ZMES_SAP_003.Response");
expression.setNamespaceURI("ns0");
expression.setTargetNamespace("urn:sap-com:document:sap:rfc:functions");
final ZMESSAP003Response response = SOAPClient.extractResponse(xml, expression, ZMESSAP003Response.class);

其中ZMESSAP003 是cxf生成的本地客户端代码,本文不是最终代码,仅供各位参考。

需要用到的pom依赖

hutool

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

 

posted on 2022-01-22 10:12  你不知道的浪漫  阅读(748)  评论(0编辑  收藏  举报