天天看點

調用需求Http Basic身份驗證的SAP Webservice

背景

筆者目前從事制造業相關行業軟體工作,因工作需要,在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即可生成本地用戶端調用代碼

調用需求Http Basic身份驗證的SAP Webservice

 這是一是正常情況,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: <[email protected]>
content-disposition: attachment;filename="[email protected]"
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生成的本地用戶端代碼,本文不是最終代碼,僅供各位參考。

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