背景
最近接手了一个支付项目,该项目对接的第三方支付公司的接口报文是XML格式,项目中使用的是XStream 进行处理的。经过排查发现每个接口响应的报文格式是统一的。于是对响应体进行了封装。如下:
@Data
@XStreamAlias("ROOT")
public class CiticApiResponse<T extends CiticBaseResponseData> {
/**
* 平台应答码
*/
private String CODE;
/**
* 平台应答码描述
*/
private String MESSAGE;
/**
* 业务响应数据
*/
private T DATA;
public boolean isSuccess(){
if ("AAAAAAA".equals(CODE)){
return true;
}
return false;
}
}
但是在进行测试的时候,xml 反序列化的时候data的数据一直报错,T 被转化为了CiticBaseResponseData ,而这个类是一个父类,封装了一些公用的字段。报文中的元素不在这个公共类中就一直报错。后面查看相关文档,发现XStream默认是不支持泛型的,但是它提供了一个Converter转化接口。通过这个接口可以自己实现反序列化。这里我就是使用这种方式解决了我的问题。 下面是我实现的内容
实现
第一步:先定义CiticApiResponseConverter 实现Converter接口
package com.ksbl.pay.client.converter;
import com.ksbl.pay.client.resp.citic.CiticApiResponse;
import com.ksbl.pay.client.resp.citic.CiticBaseResponseData;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CiticApiResponseConverter<T extends CiticBaseResponseData> implements com.thoughtworks.xstream.converters.Converter {
private Class<T> aClass;
public CiticApiResponseConverter(Class<T> aClass) {
this.aClass = aClass;
}
@Override
public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) {
}
@Override
public CiticApiResponse<T> unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
CiticApiResponse<T> response = new CiticApiResponse();
while (reader.hasMoreChildren()) {
reader.moveDown();
if ("CODE".equals(reader.getNodeName())) {
response.setCODE(reader.getValue());
} else if ("MESSAGE".equals(reader.getNodeName())) {
response.setMESSAGE(reader.getValue());
} else if ("DATA".equals(reader.getNodeName())) {
T t ;
try {
t = aClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
Map<String, Field> fieldMap = getFieldMap(aClass);
Map<String, Field> fieldAnnotationMap= fieldMap.entrySet().stream()
.filter(entry -> entry.getValue().isAnnotationPresent(XStreamAlias.class))
.collect(Collectors.toMap(entry -> entry.getValue().getAnnotation(XStreamAlias.class).value(), entry -> entry.getValue()));
while (reader.hasMoreChildren()){
reader.moveDown();
//优先使用注解 没有注解 使用字段名称
Field field = fieldAnnotationMap.get(reader.getNodeName()) != null ?fieldAnnotationMap.get(reader.getNodeName()) : fieldMap.get(reader.getNodeName());
if (field == null){
continue;
}
field.setAccessible(true);
try {
field.set(t,reader.getValue());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
reader.moveUp();
}
response.setDATA(t);
reader.moveUp(); // 返回上一层
}
reader.moveUp();
}
return response;
}
@Override
public boolean canConvert(Class aClass) {
if (aClass.getName().equals(CiticApiResponse.class.getName())) {
return true;
}
return false;
}
public Map<String, Field> getFieldMap(Class clazz){
Map<String, Field> fieldMap = Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toMap(field -> field.getName(), Function.identity()));
Class superClass = clazz.getSuperclass();
if (superClass != null){
fieldMap.putAll(getFieldMap(superClass));
}
return fieldMap;
}
}
定义一个XstreamUtils 工具类
public class XstreamUtils {
/**
*
* @param xml
* @param tClass CiticApiResponse<T> 中 T 对应的字节码对象
* @return CiticApiResponse 对象
*/
public static <T extends CiticBaseResponseData> CiticApiResponse<T> toBean(String xml, Class<T> tClass) {
XStream stream = new XStream();
stream.processAnnotations(CiticApiResponse.class);
stream.autodetectAnnotations(true);
// 注册自己实现的Converter
stream.registerConverter(new CiticApiResponseConverter(tClass));
stream.setClassLoader(CiticApiResponse.class.getClassLoader());
return (CiticApiResponse) stream.fromXML(xml);
}
public static String toXml(Object obj) {
XStream stream = new XStream(new Xpp3DomDriver(new NoNameCoder()));
stream.processAnnotations(obj.getClass());
stream.autodetectAnnotations(true);
stream.setClassLoader(obj.getClass().getClassLoader());
return stream.toXML(obj);
}
}
定义实体类
/** 通用数据对象*/
@Data
public class CiticBaseResponseData {
@XStreamAlias("RSP_CODE")
protected String RSP_CODE;//电商管家应答码
@XStreamAlias("RSP_MSG")
protected String RSP_MSG;//电商管家应答码描述
@XStreamAlias("REQ_SN")
protected String REQ_SSN;//发起方流水号
@XStreamAlias("SIGN_TYPE")
protected String SIGN_INFO;//签名
@XStreamAlias("MCHNT_ID")
protected String MCHNT_ID;
public boolean isSuccess(){
return "00000".equals(RSP_CODE);
}
}
/**注册用户响应数据*/
@Data
@EqualsAndHashCode(callSuper = true)
@XStreamAlias("DATA")
public class RegisterResponseData extends CiticBaseResponseData{
private String USER_ID;//用户编号
private String PWDID;//动态密码句柄
private String TRANS_ID;
private String IS_NEED_CHECK;//是否需要审核
@Override
public String toString() {
return "T21000001ResponseData{" +
"USER_ID='" + USER_ID + '\'' +
", PWDID='" + PWDID + '\'' +
", TRANS_ID='" + TRANS_ID + '\'' +
", IS_NEED_CHECK='" + IS_NEED_CHECK + '\'' +
", RSP_CODE='" + RSP_CODE + '\'' +
", RSP_MSG='" + RSP_MSG + '\'' +
", REQ_SSN='" + REQ_SSN + '\'' +
", SIGN_INFO='" + SIGN_INFO + '\'' +
", MCHNT_ID='" + MCHNT_ID + '\'' +
'}';
}
}
/** 通用响应对象*/
@Data
@XStreamAlias("ROOT")
public class CiticApiResponse<T extends CiticBaseResponseData> {
/**
* 平台应答码
*/
private String CODE;
/**
* 平台应答码描述
*/
private String MESSAGE;
/**
* 业务响应数据
*/
private T DATA;
public boolean isSuccess(){
if ("AAAAAAA".equals(CODE)){
return true;
}
return false;
}
}
测试使用
public class Test {
public static void main(String[] args) {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ROOT> <CODE>AAAAAAA</CODE> <DATA>\t<RSP_CODE>00000</RSP_CODE>\t<RSP_MSG></RSP_MSG>\t<REQ_SSN>J0xxxxx0000000020240604160821363k9oi9nlh</REQ_SSN>\t<SIGN_INFO>MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYIBtjCCAbICAQEwMDArMQswCQYDVQQGEwJDTjENMAsGA1UECwwEUFROUjENMAsGA1UEAwwEdGVzdAIBMDAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjQwNjA0MDgwODIxWjAjBgkqhkiG9w0BCQQxFgQUdZUvvIdt6lRImwbNJCuXtGNAsiAwDQYJKoZIhvcNAQEBBQAEggEAguhMfaIYGAkZbZIExAUfBlD/o+MnhYKIeo3zP6wH+1Dj87S0a6HiOP+GksjA0EZJVm7GhugVjq0JHO1NvzCONxSR7VLgcTp9S1RzCUDnx1AmJ/iy9l1gYC64HJSiP5ZwZ8SKjk4jB49zQe2e4NxzoaBQ1rDvMKQHyWUE1lIEvMb6XLp/IODb02xPxSFym4oz2dXWn3pGLB1zF+dmp5BXFPc4tDrDFFkIIAWoTAWWOQ8tKxofci9P+q9MbHCGZQP2DcLS4zQGlLho9qCyULOk97I5v0NVKa0LGYH7NXjqxkngDmHhBWNQTIJMiKR2h38C7WrSJiQ8nDD1FIg9JTJI7AAAAAAAAA==</SIGN_INFO>\t<USER_ID>J04025500000002</USER_ID>\t<TRANS_ID>123</TRANS_ID>\t<PWDID>xxxxx</PWDID> </DATA> <MESSAGE>SUCCESS</MESSAGE></ROOT>\n";
CiticApiResponse<RegisterResponseData> response = XstreamUtils.toBean2(xml, RegisterResponseData.class);
System.out.println(response);
}
}
CiticApiResponse(CODE=AAAAAAA, MESSAGE=null, DATA=T21000001ResponseData{USER_ID='J04025500000002', PWDID='xxxxx', TRANS_ID='123', IS_NEED_CHECK='null', RSP_CODE='00000', RSP_MSG='', REQ_SSN='J0xxxxx0000000020240604160821363k9oi9nlh', SIGN_INFO='MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYIBtjCCAbICAQEwMDArMQswCQYDVQQGEwJDTjENMAsGA1UECwwEUFROUjENMAsGA1UEAwwEdGVzdAIBMDAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjQwNjA0MDgwODIxWjAjBgkqhkiG9w0BCQQxFgQUdZUvvIdt6lRImwbNJCuXtGNAsiAwDQYJKoZIhvcNAQEBBQAEggEAguhMfaIYGAkZbZIExAUfBlD/o+MnhYKIeo3zP6wH+1Dj87S0a6HiOP+GksjA0EZJVm7GhugVjq0JHO1NvzCONxSR7VLgcTp9S1RzCUDnx1AmJ/iy9l1gYC64HJSiP5ZwZ8SKjk4jB49zQe2e4NxzoaBQ1rDvMKQHyWUE1lIEvMb6XLp/IODb02xPxSFym4oz2dXWn3pGLB1zF+dmp5BXFPc4tDrDFFkIIAWoTAWWOQ8tKxofci9P+q9MbHCGZQP2DcLS4zQGlLho9qCyULOk97I5v0NVKa0LGYH7NXjqxkngDmHhBWNQTIJMiKR2h38C7WrSJiQ8nDD1FIg9JTJI7AAAAAAAAA==', MCHNT_ID='null'})
到此问题成功解决。
该方法是我自己实现的,解决了我目前使用场景中的问题。
分享该内容也是提供一个解决问题的思路,如过有更好的解决方案,欢迎留言