背景

笔者当前从事制造业相关行业软件工作,因工作需要,在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_java

 

 

 这是一是常规情况,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>