本文提供两种方式:
方式一:解析固定格式xml报文更方便,但未找到使用泛型时,解析的方式;
方式二:可根据不同接口,报文不同,改变实体(更复杂,功能更强大);
方式三:可使用泛型;
方法一
所需依赖:
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--解析xml报文-->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
发送xml报文请求:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class HttpPostXml {
//获取数据超时时间60秒
private final int OVERTIME = 60000;
/*@Value("${sysHead.userLang}")
private String userLang;*/
/**
* 创建连接,发送xml请求报文,获取响应报文
*
* @param urlStr 请求路径
* @param bodyStr body标签数据字符串
* @return
*/
public String httpRequestAndTransData(InputSysHead sysHead, InputAppHead appHead, String urlStr, String bodyStr) {
String line = "";
StringBuffer resXmlStr = new StringBuffer();
try {
//1.声明URL
URL url = new URL(urlStr);
//2.创建链接
URLConnection con = url.openConnection();
//3.封装报文传输进行传输
//调用getXmlInfo()进行报文封装
String xmlInfo = getXmlInfo(sysHead, appHead, bodyStr);
byte[] xmlData = xmlInfo.getBytes();
con.setDoOutput(true);
con.setDoInput(true);
con.setUseCaches(false);
con.setRequestProperty("Pragma:", "no-cache"); //指示请求或响应消息不能缓存
/*
* Cache-Control 指定请求和响应遵循的缓存机制
* 在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程
* 请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached
* 响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age
* Public指示响应可被任何缓存区缓存。
* Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
* no-cache指示请求或响应消息不能缓存
* no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
* max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
* min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
* max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
*/
con.setRequestProperty("Cache-Control", "no-cache");
con.setRequestProperty("Content-Type", "text/xml");
//设置超时时间60s
con.setReadTimeout(OVERTIME);
con.setRequestProperty("Content-length", String.valueOf(xmlData.length));
OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream());
out.write(new String(xmlInfo.getBytes("ISO-8859-1")));
out.flush();
out.close();
//4.获取响应报文
BufferedReader br = new BufferedReader(new InputStreamReader(
con.getInputStream()));
//返回响应报文
for (line = br.readLine(); line != null; line = br.readLine()) {
resXmlStr.append(line);
}
return resXmlStr.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resXmlStr.toString();
}
/**
* 接收数据,拼接生成请求报文。需要根据需求自行添加body标签
*
* @param sysHead 输入系统头
* @param appHead 输出应用头
* @param bodyStr body标签数据字符串
* @return
*/
private static String getXmlInfo(InputSysHead sysHead, InputAppHead appHead, String bodyStr) {
// 系统头和应用头标签是死的.数据是活的,根据接口不同变化,body标签、数据是活的 (读配置文件)
// 系统头和应用头
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
sb.append("<service>");
sb.append(" <SYS_HEAD>");
sb.append(" <SvcCd>" + sysHead.getSvcCd() + "</SvcCd>");
sb.append(" <SvcScn>" + sysHead.getSvcScn() + "</SvcScn>");
sb.append(" <CnsmSysId>" + sysHead.getCnsmSysId() + "</CnsmSysId>");
sb.append(" <ChnlTp>" + sysHead.getChnlTp() + "</ChnlTp>");
sb.append(" <SrcSysId>" + sysHead.getSrcSysId() + "</SrcSysId>");
sb.append(" <SrcSysSeqNo>" + sysHead.getSrcSysSeqNo() + "</SrcSysSeqNo>");
sb.append(" <CnsmSysSeqNo>" + sysHead.getCnsmSysSeqNo() + "</CnsmSysSeqNo>");
sb.append(" <Mac/>");
sb.append(" <TranMD>" + sysHead.getTranDt() + "</TranMD>");
sb.append(" <TranDt>" + sysHead.getTranDt() + "</TranDt>");
sb.append(" <TranTm>" + sysHead.getTranTm() + "</TranTm>");
sb.append(" <TmnlNo>" + sysHead.getTmnlNo() + "</TmnlNo>");
sb.append(" <SrcSysTmnlNo/>");
sb.append(" <CnsmSysSvrId/>");
sb.append(" <SrcSysSvrId/>");
sb.append(" </SYS_HEAD>");
sb.append(" <APP_HEAD>");
sb.append(" <TlrNo>" + appHead.getTlrNo() + "</TlrNo>");
sb.append(" <BranchId>" + appHead.getBranchId() + "</BranchId>");
sb.append(" <TlrPswd/>");
sb.append(" <TlrLvl/>");
sb.append(" <TlrTp/>");
sb.append(" <AprvFlg/>");
sb.append(" <AuthFlg/>");
sb.append(" </APP_HEAD>");
/*sb.append(" <BODY>");
sb.append(" <AuthTlrStts>" + authTlrStts + "</AuthTlrStts>");
sb.append(" <InfoNo>" + infoNo + "</InfoNo>");
sb.append(" </BODY>");*/
// sb.append("</service>");
return sb.toString() + bodyStr;
}
}
解析响应报文:
public class XmlToBean {
public ServiceXml readStringXml(String xmlStr) {
XStream xs = new XStream();
// 设置默认安全性。解决Security framework of XStream not initialized, XStream is probably vulnerable.
XStream.setupDefaultSecurity(xs);
// 允许类型
xs.allowTypes(new Class[]{ServiceXml.class});
// 转换类型
xs.processAnnotations(new Class[]{ServiceXml.class});
//把xml转成对象,强转成ServiceXml对象
ServiceXml obj = (ServiceXml) xs.fromXML(xmlStr);
return obj;
}
}
实体:
ServiceXml 类:
@XStreamAlias("service")
public class ServiceXml<T> {
@XStreamAlias("SYS_HEAD")
private OutSysHead sysHead;
@XStreamAlias("APP_HEAD")
private OutAppHead appHead;
@XStreamAlias("BODY")
private Body body;
// private T body;
}
OutAppHead 类:
@XStreamAlias("APP_HEAD")
public class OutAppHead {
// 机构Id
@XStreamAlias("BranchId")
private String branchId;
}
OutSysHead 类:
@XStreamAlias("SYS_HEAD")
public class OutSysHead {
// 服务代码
@XStreamAlias("SvcCd")
private String svcCd;
// 服务场景
@XStreamAlias("SvcScn")
private String svcScn;
// 系统头中的array标签
@XStreamAlias("array")
private OutSysHeadArray sysHeadArray;
}
OutSysHeadArray 类:
/**
* 系统头中的array标签
*/
@XStreamAlias("array")
public class OutSysHeadArray {
// 交易返回信息数组
@XStreamImplicit(itemFieldName = "RetInf")
private List<OutSysRetInfo> retInfos;
}
OutSysRetInfo 类:
/**
* 系统头交易返回数组中元素
*/
@XStreamAlias("RetInfo")
public class OutSysRetInfo {
// 交易返回代码
@XStreamAlias("RetCode")
private String retCd;
// 交易返回信息
@XStreamAlias("RetMsg")
private String retMsg;
}
响应报文:
String xmlStr = "<?xml version="1.0" encoding="utf-8"?><service><SYS_HEAD><SvcCd>123</SvcCd>" +
"<SvcScn>22</SvcScn><array><RetInf><RetMsg>交易成功</RetMsg></RetInf></array></SYS_HEAD>" +
"<APP_HEAD><BranchId>0099</BranchId></APP_HEAD><BODY><InfoNo>0</InfoNo></BODY></service>";
问题:
以上方法,解析响应报文时,ServiceXml 使用泛型,会报错,暂未找到解决方法
方法二:
所需依赖:
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>retrofit</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.squareup.retrofit2</groupId>
<artifactId>adapter-rxjava2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.2</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>3.14.2</version>
</dependency>
application.yml
third:
url: http://locahost:9091/ # 结尾必须有/
Config类:从config类入
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Configuration
public class Config {
private static Map<String, Object> services = new HashMap<String, Object>();
private static Logger logger = LoggerFactory.getLogger(Config.class);
@Autowired
private ThirdRequestUrl thirdRequestUrl;
protected HttpLoggingInterceptor loggingInterceptor() {
//Log相关
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String s) {
logger.info(s);
}
});
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return logging;
}
/**
* 缺省OKHttp配置
*
* @return
*/
private OkHttpClient.Builder getdefOkhttp() {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
okHttpClient.connectTimeout(60, TimeUnit.SECONDS); //连接超时时间
okHttpClient.readTimeout(60, TimeUnit.SECONDS); //读取超时时间
okHttpClient.writeTimeout(60, TimeUnit.SECONDS); //写超时
okHttpClient.addInterceptor(loggingInterceptor()); //写日志拦截器
okHttpClient.addInterceptor(customHttpLogInterceptor()); //自定义日志拦截器
//失败重连
okHttpClient.retryOnConnectionFailure(true);
return okHttpClient;
}
// 拦截器的加载在springcontext之前,所以自动注入的mapper是null,需要添加拦截器之前用@bean注解将拦截器注入工厂,接着添加拦截器
@Bean
public CustomHttpLogInterceptor customHttpLogInterceptor(){
return new CustomHttpLogInterceptor();
}
protected <T> T createService(Class<T> serviceClass, String baseUrl) {
OkHttpClient.Builder okHttpClient = getdefOkhttp();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
//设置OKHttpClient
.client(okHttpClient.build())
.addConverterFactory(JaxbConverterFactory.create())
// .addConverterFactory(JacksonConverterFactory.create()) // json 格式转换
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//
.build();
T service = retrofit.create(serviceClass);
return service;
}
// 入口
@Bean
public CardServiceAPI cardServiceAPI() {
return this.createService(CardServiceAPI.class, thirdRequestUrl.getCards());
}
}
CardServiceAPI接口:
import org.springframework.stereotype.Repository;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
@Repository
public interface CardServiceAPI {
@POST("testCard") // 发送post请求,必须要有路径。即"testCard"不可少
Call<CardMessage> selectCard(@Body CardMessage message);
}
说明:
1、service层注入此接口;
2、若同一URL(ip + 端口)下,仅具体的“testCard”不同,可写在同一个XxxServiceAPI接口中,改变“testCard”即可。
3、“testCard”不为“/testCard”
JaxbRequestConverter类:请求转换器:请求实体转换为xml
import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
import okhttp3.RequestBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Converter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.*;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
final class JaxbRequestConverter<T> implements Converter<T, RequestBody> {
private static Logger logger = LoggerFactory.getLogger(JaxbRequestConverter.class);
final JAXBContext context;
final Class<T> type;
JaxbRequestConverter(JAXBContext context, Class<T> type) {
this.context = context;
this.type = type;
}
public RequestBody convert(T value) throws IOException {
StringBuilder data = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
try {
StringWriter original = new StringWriter();
Marshaller marshaller = this.context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, JaxbConverterFactory.XML.charset().name());// //编码格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);// 是否格式化生成的xml串
// 不进行转义字符的处理
marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() {
public void escape(char[] ch, int start,int length, boolean isAttVal, Writer writer) throws IOException {
writer.write(ch, start, length);
}
});
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);// 是否省略xm头声明信息
marshaller.marshal(value, original);
logger.debug(original.toString());
data.append(original.toString());
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return RequestBody.create(JaxbConverterFactory.XML, data.toString());
}
}
JaxbResponseConverter类:响应转换器:响应报文xml转为实体
import com.oneconnect.sg.hub.client.utils.AesCbcPkcs5Base64Utils;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Converter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.StringReader;
final class JaxbResponseConverter<T> implements Converter<ResponseBody, T> {
private static Logger logger = LoggerFactory.getLogger(JaxbResponseConverter.class);
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final JAXBContext context;
final Class<T> type;
JaxbResponseConverter(JAXBContext context, Class<T> type) {
this.context = context;
this.type = type;
}
public T convert(ResponseBody value) throws IOException {
try {
StringBuilder data = new StringBuilder(value.string());
//String key = SpringUtils.getProperty("app.owsdl.encrypt.key");
String key = "qyxjjb74cidnc16b"; // 加密秘钥
String iv = "xf073abfru46awwt"; // 解密秘钥
// String iv = SpringUtils.getProperty("app.owsdl.encrypt.iv");
String cKey = key + "|" + iv;
if (data.indexOf("<body>") > -1) {
int start = data.indexOf("<body>") + 6;
int end = data.indexOf("</body>");
String body = data.substring(start, end);
body = body.trim();
String de = AesCbcPkcs5Base64Utils.getDecryString(body, cKey);
data.replace(start, end, de);
}
logger.debug(data.toString());
Unmarshaller unmarshaller = this.context.createUnmarshaller();
XMLStreamReader streamReader = this.xmlInputFactory.createXMLStreamReader(new StringReader(data.toString()));
return unmarshaller.unmarshal(streamReader, this.type).getValue();
} catch (XMLStreamException | JAXBException var4) {
throw new RuntimeException(var4);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
JaxbConverterFactory类:转换器工厂
import io.reactivex.annotations.Nullable;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlRootElement;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
public class JaxbConverterFactory extends Converter.Factory {
static final MediaType XML = MediaType.parse("application/xml; charset=utf-8");
@Nullable
private final JAXBContext context;
public static JaxbConverterFactory create() {
return new JaxbConverterFactory(null);
}
public static JaxbConverterFactory create(JAXBContext context) {
if (context == null) {
throw new NullPointerException("context == null");
} else {
return new JaxbConverterFactory(context);
}
}
private JaxbConverterFactory(@Nullable JAXBContext context) {
this.context = context;
}
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return type instanceof Class && ((Class)type).isAnnotationPresent(XmlRootElement.class) ? new JaxbRequestConverter(this.contextForType((Class)type), (Class)type) : null;
}
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return type instanceof Class && ((Class)type).isAnnotationPresent(XmlRootElement.class) ? new JaxbResponseConverter(this.contextForType((Class)type), (Class)type) : null;
}
private JAXBContext contextForType(Class<?> type) {
try {
return this.context != null ? this.context : JAXBContext.newInstance(type);
} catch (JAXBException var3) {
throw new IllegalArgumentException(var3);
}
}
}
实体:
Message类:
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.NONE)
//@XmlRootElement(name = "service")
//@XmlSeeAlso({String.class, CustomerBody.class, CardBody.class,})
public class Message {
@XmlElement(name = "SYS_HEAD")
private SysHead sysHead;
@XmlElement(name = "APP_HEAD")
private AppHead appHead;
// @XmlAnyElement(lax = true)
// private T body;
public SysHead getSysHead() {
return sysHead;
}
public void setSysHead(SysHead sysHead) {
this.sysHead = sysHead;
}
public AppHead getAppHead() {
return appHead;
}
public void setAppHead(AppHead appHead) {
this.appHead = appHead;
}
// public T getBody() {
// return body;
// }
//
// public void setBody(T body) {
// this.body = body;
// }
}
SysHead类:固定的(所有请求报文xml、响应报文xml都有的标签、参数)
@XmlAccessorType(XmlAccessType.NONE) //所有属性都不映射为xml的元素
@Data
public class SysHead {
// 服务代码
@XmlElement(name = "SvcCd")
private String svcCd;
// 服务场景
@XmlElement(name = "SvcScn")
private String svcScn;
}
AppHead类:固定的(所有请求报文xml、响应报文xml都有的标签、参数)
@XmlAccessorType(XmlAccessType.NONE) //所有属性都不映射为xml的元素
@Data
public class AppHead {
// T号
@XmlElement(name = "TlrNo")
private String tlrNo;
// 机构Id
@XmlElement(name = "BranchId")
private String branchId;
}
CardMessage类:可变的(根据请求不同而改变。请求报文xml、响应报文xml中BODY标签中的参数不同)
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({CardBody.class})
@XmlRootElement(name = "service") // 标签名
public class CardMessage extends Message {
@XmlElement(name = "BODY")
private CardBody body;
public CardBody getBody() {
return body;
}
public void setBody(CardBody body) {
this.body = body;
}
}
CardBody类:可变的(根据请求不同而改变。请求报文xml、响应报文xml中BODY标签中的参数不同)
@XmlAccessorType(XmlAccessType.NONE)
//@XmlRootElement(name = "BODY") //标签名 在CardMessage中有@XmlElement(name = "BODY")后,可不写此注解
@ApiModel("卡信息")
@Data
public class CardBody {
/**
* 卡号
*/
@ApiModelProperty("卡号")
@XmlElement(name = "CstNo") //标签名
private String cstNo;
/**
* 客户名
*/
@ApiModelProperty("客户名")
@XmlElement(name = "CstNm")
private String cstNm;
}
方法三:
使用泛型。
import com.alibaba.druid.util.StringUtils;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
public class XmlToEntity {
public static <T> T xmlToBean(String xmlStr, Class<T> body) throws JAXBException {
if (null == body || StringUtils.isEmpty(xmlStr)) {
return null;
}
JAXBContext context = JAXBContext.newInstance(body);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader stringReader = new StringReader(xmlStr);
Object unmarshal = unmarshaller.unmarshal(stringReader);
return (T) unmarshal;
}
public static <T> String beanToXml(Class<T> body) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(body);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
//去掉请求头
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
StringWriter stringWriter = new StringWriter();
stringWriter.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
marshaller.marshal(body, stringWriter);
return stringWriter.toString();
}
}
说明:
1、根据Body的不同,新建不同XxxBody,同时需要新建XxxMessage类继承Message类;
2、SysHead类、AppHead类为报文中固定的标签。CardBody类根据请求不同,报文中的BODY标签不同,而对应改变;
3、使用泛型;
————以上,两种方式,欢迎交流————