cglib与xstream结合构造webservice的xml格式的入参返参的动态生成
在做项目的时候遇到这样一个问题,需要与另一个系统进行Webservice通信,通信的入参、返参均是XML格式的,如下:
<PARAM>
<DET_FLAG>1</DET_FLAG>
<IO_TASK_NO>4413051710269522</IO_TASK_NO>
</PARAM>
但是这样的接口很多,而且大部分都是简单的几个属性,所以不想为每一种入参和返参都新建一个POJO类,于是就想到用Cglib动态生成类,然后再利用Xstream进行转换。
首先从网上摘了一段代码,如下:
package com.nari.component.xmlconvertor; import java.util.Iterator; import java.util.Map; import java.util.Set; import net.sf.cglib.beans.BeanGenerator; import net.sf.cglib.beans.BeanMap; public class CglibBean { public Object object = null; public BeanMap beanMap = null; public CglibBean() { super(); } public CglibBean(Map<String,?> propertyMap) throws Exception { this.object = generateBean(propertyMap); this.beanMap = BeanMap.create(this.object); } public void setValue(String property, Object value) { beanMap.put(property, value); } public Object getValue(String property) { return beanMap.get(property); } public Object getObject() { return this.object; } private Object generateBean(Map<String,?> propertyMap) throws Exception { BeanGenerator generator = new BeanGenerator(); Set<String> keySet = propertyMap.keySet(); for (Iterator<String> i = keySet.iterator(); i.hasNext();) { String key = i.next(); //修改为获取属性的Class类型 提高兼容性 generator.addProperty(key, propertyMap.get(key).getClass()); } return generator.create(); } }
这段代码就是生成了一个BeanGenerator,没什么好看的,网上一堆一堆的解释,不过将generator.addProperty的参数修改为了propertyMap.get(key).getClass()),以适应自己的需求,而且需要注意的问题是如果是web运行环境,一定要排查下依赖的jar包是否有冲突。
然后,就是写自己的转换类了,先看下面的代码,这个是将给定的Map转换为XML格式的String
package com.nari.component.xmlconvertor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import com.thoughtworks.xstream.XStream; public class XMLConvertor { public static String convertor(Map<String,?> map,String type) throws Exception { //实例化CglibBean CglibBean bean = new CglibBean(map); //为字段赋值,如果不赋值则xml中就没有这个字段 for(String mapkey : map.keySet()){ bean.setValue(mapkey, map.get(mapkey)); System.out.println(mapkey + " = " + map.get(mapkey)); } //定制xml格式 Object object = bean.getObject(); Class<?> clazz = object.getClass(); Field[] fields = clazz.getDeclaredFields(); XStream x = new XStream(); //修改头为PARAM x.alias(type, object.getClass()); //alias属性为对应的属性名 for (int i = 0; i < fields.length; i++) { x.aliasField(fields[i].getName().substring(12,fields[i].getName().length()), object.getClass(), fields[i].getName()); } //返回转换好的xml文件 String xml = x.toXML(object); //不知对方接受的xml文件是否要去除空格,如果需要则可使用下面注释掉的方法 //xstream有bug,当参数中含有下划线的时候会转换为双下划线,这里做简单处理 return xml.replaceAll("__", "_"); } }
注释都在类里面,需要注意的是一些xstream的alian的问题,仔细看下,没啥东西。
其次,是将给定的XML格式字符串转换为自己需要的MAP,代码如下:
package com.nari.component.xmlconvertor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class StringConvertor { public static Map<String,String> convertor(Map<String,String> map,String xml,String type) throws Exception { CglibBean bean = new CglibBean(map); Object obj = bean.getObject(); Class<?> clazz = obj.getClass(); XStream xs = new XStream (new DomDriver()); xs.alias(type, clazz); Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { xs.aliasField(fields[i].getName().substring(12,fields[i].getName().length()), clazz, fields[i].getName()); } Object obj2 = (Object)xs.fromXML(xml); Class<?> clazz2 = obj2.getClass(); Method[] m3 = clazz2.getDeclaredMethods(); for(Method method :m3){ if(method.getName().indexOf("get")>-1){ String methodName = method.getName(); System.out.println(methodName); String value = (String)method.invoke(obj2, new Object[0]); String key = methodName.substring(3,methodName.length()); map.put(key, value); } } return map; } }
其实就是一个动态类的转换,注意字段的对应关系就行了。
最后 ,是一个测试类,这里有一些xfire客户端的东西,可以删掉
package com.nari.webservice.client; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import com.nari.component.xfireclient.XFireClient; import com.nari.component.xmlconvertor.StringConvertor; import com.nari.component.xmlconvertor.XMLConvertor; public class TestClient { public static void main(String args[]) throws Exception{ String IO_TASK_NO = "4413051710269522"; String DET_FLAG = "1"; Object[] params = new Object[]{IO_TASK_NO,DET_FLAG}; Map<String,String> m = new HashMap<String,String>(); m.put("IO_TASK_NO", "4413051710269522"); m.put("DET_FLAG", "1"); String s = ""; try { s = XMLConvertor.convertor(m, "PARAM"); System.out.println(s); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } Map<String,String> ma1 = new HashMap<String,String>(); ma1.put("IO_TASK_NO", ""); ma1.put("DET_FLAG", ""); ma1 = StringConvertor.convertor(ma1, s, "PARAM"); System.out.println(ma1.get("IO_TASK_NO")); System.out.println(ma1.get("DET_FLAG")); XFireClient client = new XFireClient(); Object[] backXml = client.callService("setInputTask", params); String backString = (String)backXml[0]; } }
其实,这里的入参和返参都是最基本的XML格式,没有内部属性,没有内部集合,用上面两个工具类足够了,下面的这个测试类提供了对复杂XML格式的动态转换,仅仅作为一个demo,代码如下:
package com.nari.component.xmlconvertor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.thoughtworks.xstream.XStream; public class TestXStream { public static void main(String args[]) throws Exception{ //collection demo List<Object> list = new ArrayList<Object>(); //不知道什么原因,这里new的虚拟类设置不进去值,可以用下面反射的方法想办法按照一定的规则去赋值,这个是特殊用法,一般不用,这里做兼容性支持 //原因已经找到,是因为在MAP中使用了关键字,这点估计后台截取的时候会导致类似问题,尽量避免关键字,否则可能需要反射调用,开销很大!! for(int iii = 0;iii<2; iii++){ Map<String,String> map01 = new HashMap<String,String>(); map01.put("ASD1", "asd1"); map01.put("CWE", "asd2"); map01.put("ASD3", "asd3"); CglibBean innerBean11 = new CglibBean(map01); for(String mapkey : map01.keySet()){ innerBean11.setValue(mapkey, map01.get(mapkey)); } Map<String,String> dymaMap = new HashMap<String,String>(); dymaMap.put("XXXX", "AAS"); dymaMap.put("WWWW", "ASD"); dymaMap.put("WEWEW", "dsa"); CglibBean dymaBean = new CglibBean(dymaMap); for(String mapkey : dymaMap.keySet()){ dymaBean.setValue(mapkey, dymaMap.get(mapkey)); } list.add(innerBean11.getObject()); list.add(dymaBean.getObject()); Object objo = dymaBean.getObject(); Class<?> clazz = objo.getClass(); Field[] testfield = clazz.getDeclaredFields(); Method[] m = clazz.getDeclaredMethods(); for(Method method :m){ if(method.getName().indexOf("get")>-1){ String s = (String)method.invoke(objo, new Object[0]); System.out.println("从虚拟类中中取出的值为 : " + s); } } } //内部属性demo Map<String,String> map0 = new HashMap<String,String>(); map0.put("ASD1", "asd1"); map0.put("ASD2", "asd2"); map0.put("ASD3", "asd3"); CglibBean innerBean1 = new CglibBean(map0); for(String mapkey : map0.keySet()){ innerBean1.setValue(mapkey, map0.get(mapkey)); } Object innerBean1Obj = innerBean1.getObject(); Class<?> clazz3 = innerBean1Obj.getClass(); Method[] m3 = clazz3.getDeclaredMethods(); for(Method method :m3){ if(method.getName().indexOf("get")>-1){ String s = (String)method.invoke(innerBean1Obj, new Object[0]); System.out.println("22222从虚拟类中中取出的值为 : " + s); } } //原始 Map<String,Object> map = new HashMap<String,Object>(); map.put("TEST1", "testValue1"); map.put("TEST2", "testValue2"); map.put("TEST3", innerBean1.getObject()); map.put("TEST4", list); CglibBean bean = new CglibBean(map); for(String mapkey : map.keySet()){ bean.setValue(mapkey, map.get(mapkey)); } Object object = bean.getObject(); XStream xstream = new XStream(); Class<?> clazz = object.getClass(); Field[] fields = clazz.getDeclaredFields(); //别名 xstream.alias("ROOT", clazz); for (int i = 0; i < fields.length; i++) { //简化的别名 String aliasName = fields[i].getName().substring(12,fields[i].getName().length()); xstream.aliasField(aliasName, object.getClass(), fields[i].getName()); //如果不是String类型的则表明是Object的具体对象 //如果是List if(bean.getValue(aliasName) instanceof ArrayList){ xstream.addImplicitCollection(clazz, fields[i].getName()); ArrayList arraylist = (ArrayList)bean.getValue(aliasName); for(Object objectBean : arraylist){ Class<?> clazzList = objectBean.getClass(); Field[] fieldsList = clazzList.getDeclaredFields(); xstream.alias(aliasName, clazzList); Method[] methods = clazzList.getDeclaredMethods(); for(Method m : methods){ if(m.getName().indexOf("set")>-1){ // m.invoke(objectBean, new Object[]{"tt!!!"}); } } for (int j = 0; j < fieldsList.length; j++) { // String s = fieldsList[j].getName() ; String aliasNameInner1 = fieldsList[j].getName().substring(12,fieldsList[j].getName().length()); xstream.aliasField(aliasNameInner1, clazzList, fieldsList[j].getName()); xstream.useAttributeFor(clazzList,fieldsList[j].getName()); } } System.out.println(aliasName + " 是List"); } //如果是String else if(bean.getValue(aliasName) instanceof String){ System.out.println(aliasName + " 是String"); } //如果是内部类 else { System.out.println(aliasName + " 是单独的类"); Class<?> clazzInner = bean.getValue(aliasName).getClass(); Field[] fieldsInner = clazzInner.getDeclaredFields(); for (int ii = 0; ii < fieldsInner.length; ii++) { String aliasNameInner = fieldsInner[ii].getName().substring(12,fieldsInner[ii].getName().length()); xstream.aliasField(aliasNameInner, clazzInner, fieldsInner[ii].getName()); xstream.useAttributeFor(clazzInner,fieldsInner[ii].getName()); } } xstream.useAttributeFor(object.getClass(),fields[i].getName()); } System.out.println(xstream.toXML(object)); } }
引入上面相关类后可以直接运行,运行结果如下:
<ROOT TEST1="testValue1" TEST2="testValue2">
<TEST3 ASD2="asd2" ASD3="asd3" ASD1="asd1"/>
<TEST4 ASD3="asd3" ASD1="asd1" CWE="asd2"/>
<TEST4 WEWEW="dsa" WWWW="ASD" XXXX="AAS"/>
<TEST4 ASD3="asd3" ASD1="asd1" CWE="asd2"/>
<TEST4 WEWEW="dsa" WWWW="ASD" XXXX="AAS"/>
</ROOT>
对于复杂XML转换为动态对象的工具类暂时没有写,以后补上