由于目前在工作中一直用的dom4j+反射实现bean与xml的相互转换,记录一下,如果有不正确的地方欢迎大家指正~~~

一、反射机制

在此工具类中使用到了反射技术,所以提前也看了一些知识点,例如:http://www.51gjie.com/java/82(这里面有大量的例子可以学习)

二、dom4j

dom4j会将整个xml加载至内存,并解析成一个document对象,但是可能会造成内存溢出现象。

Document:表示整个xml文档。文档Document对象是通常被称为DOM树。

Element:表示一个xml元素。Element对象有方法来操作其子元素,它的文本,属性和名称空间

Attribute:表示元素的属性。属性有方法来获取和设置属性的值。它有父节点和属性类型。

Node:代表元素,属性或者处理指令。

三、dom4j读取xml

读取xml文档主要依赖于org.dom4j.io包,翻看其中源码可以看出提供DOMReader、SaxReader、XPPReader、XPP3Reader,我这里主要查看了SaxReader源码,其他的没有深入看过,所以使用SaxReader。

/*** 将xml字符串转换为Document对象

*@paramxml

*@return

*/

publicDocument getDocumentByString(String xml)

{//1.字符串输入流

StringReader stringReader = newStringReader(xml);//2.获取解析器

SAXReader saxReader = newSAXReader();//3.声明document对象

Document document = null;try{//4.解析xml,生成document对象

document =saxReader.read(stringReader);

}catch(DocumentException e)

{

log.error("xml解析失败",e);

}returndocument;

}

四、bean与xml的互转方法

我这里测试案例,查询学生学校信息,返回学校及多个学生信息。如下准备:

1.准备bean

请求实体bean:ReqSchool.java

public classReqSchool {//学校编号

privateString number;//学校名称

privateString name;//学校省份

privateString province;//学校地址

privateString address;//学生班级

privateString stuclass;//学生姓名

privateString stuname;//学生分数

privateString stuscore;//省略set和get方法

}

响应实体bean:RspSchool.java

public classRspSchool {//学校编号

privateString number;//学校名称

privateString name;//学校省份

privateString province;//学校地址

privateString address;//多个学生

private Liststudents;  //模拟测试数据,返回多个学生//省略get和set方法

响应实体bean的泛型:RspStudent.java

public classRspStudent {//学生班级

privateString stuclass;//学生姓名

privateString stuname;//学生分数

privateString stuscore;//省略set和get方法

}

2.准备xml

①请求模版requestXML

这里以${元素名}作为请求模版,也可以修改工具类进行改造。

${number}

${name}

${province}

${address}

${stuclass}

${stuname}

${stuscore}

②响应responseXML

0001

xxx实验小学

北京市

西城区

高三二班

李四

100

高三三班

张三

95

高三四班

王五

0

3.工具类(可直接复制粘贴使用)

复制粘贴使用时,需保证和我这里的请求报文模版相同(即使用${元素名}),当然也可手动改造此方法。

@Slf4jpublic classXmlUtil {//${abc}正则

public static String varRegex = "\\$\\{\\s*(\\w+)\\s*(([\\+\\-])\\s*(\\d+)\\s*)?\\}";/*** xml解析成document对象

*

*@paramxml

*@return

*/

publicDocument getDocument(String xml) {

StringReader stringReader= newStringReader(xml);

SAXReader saxReader= newSAXReader();

Document document= null;try{

document=saxReader.read(stringReader);

}catch(DocumentException e) {

}returndocument;

}/*** xml与bean的相互转换

*

*@paramelement

*@paramdirection 1:java2xml,2:xml2java

*@paramobj*/

public voidparseXml(Element element, String direction, Object obj) {//获取当前元素的所有子节点(在此我传入根元素)

List elements =element.elements();//判断是否有子节点

if (elements != null && elements.size() > 0)

{//进入if说明有子节点//遍历

for(Element e : elements)

{//判断转换方向(1:java2xml;2:xml2java)

if ("2".equals(direction)) //这里是xml转bean

{//声明Field

Field field = null;try{//反射获取属性

field =obj.getClass().getDeclaredField(e.getName());

}catch(Exception e1)

{

}//获取当前属性是否为list

if (field!=null&&List.class.getName().equals(field.getType().getName()))

{//反射获取set方法

Method method = this.getDeclaredMethod(obj, "set".concat(this.toUpperCaseFirstOne(e.getName())), new Class[]{List.class});//声明临时list

List temList = newArrayList();if (method!=null)

{try{//反射调用obj的当前方法,可变参数为templist

method.invoke(obj, temList);

}catch(Exception e1) {

log.info("【{}】方法执行失败",method,e1);

}

}//获取List的泛型参数类型

Type gType =field.getGenericType();//判断当前类型是否为参数化泛型

if (gType instanceofParameterizedType)

{//转换成ParameterizedType对象

ParameterizedType pType =(ParameterizedType) gType;//获得泛型类型的泛型参数(实际类型参数)

Type[] tArgs =pType.getActualTypeArguments();if (tArgs!=null&&tArgs.length>0)

{//获取当前元素的所有子元素

List elementSubList=e.elements();//遍历

for(Element e1:elementSubList) {try{//反射创建对象

Object tempObj = Class.forName(tArgs[0].getTypeName()).newInstance();

temList.add(tempObj);//递归调用自身

this.parseXml(e1, direction, tempObj);

}catch(Exception e2)

{

log.error("【{}】对象构造失败",tArgs[0].getTypeName(),e2);

}

}

}

}

}else{//说明不是list标签,继续递归调用自身即可

this.parseXml(e, direction, obj);

}

}else if("1".equals(direction)) //说明转换方向为:javabean转xml

{//递归调用自身

this.parseXml(e, direction, obj);

}//此时还在for循环遍历根元素的所有子元素

}

}else{//说明无子节点//获取当前元素的名称

String nodeName =element.getName();//获取当前元素的对应的值

String nodeValue =element.getStringValue();//判断转换方向:1:java2xml、2:xml2java

if ("1".equals(direction))//java2xml

{if (nodeValue != null &&nodeValue.matches(varRegex))

{/*** 获取模板中各节点定义的变量名,例如${traceNo}*/nodeValue= nodeValue.substring(nodeValue.indexOf("${") + 2, nodeValue.indexOf("}"));

Object value= null;//根据解析出的变量名,调用obj对象的getXXX()方法获取变量值

Method method = this.getDeclaredMethod(obj, "get".concat(this.toUpperCaseFirstOne(nodeValue)), null);if (method != null) {try{

value=method.invoke(obj);

}catch(Exception e) {

log.error("方法【{}】调用异常", "get".concat(this.toUpperCaseFirstOne(nodeValue)));

}

}//将变量值填充至xml模板变量名位置,例如${traceNo}

element.setText(value == null ? "": value.toString());

}//叶子节点

log.debug("节点名【{}】,节点变量名【{}】",element.getName(),nodeValue);

}else if ("2".equals(direction))//xml2java

{if (nodeName != null && !"".equals(nodeName))

{//根据xml节点名,调用obj对象的setXXX()方法为obj设置变量值

Method method = this.getDeclaredMethod(obj, "set".concat(this.toUpperCaseFirstOne(nodeName)), new Class[]{String.class});if(method!=null)

{try{

method.invoke(obj, nodeValue);

}catch(Exception e)

{

log.error("方法【{}】调用异常","set".concat(this.toUpperCaseFirstOne(nodeName)));

}

}

}

}

}

}private Method getDeclaredMethod(Object object, String methodName, Class>[] parameterTypes)

{for (Class> superClass = object.getClass(); superClass != Object.class; superClass =superClass.getSuperclass())

{try{returnsuperClass.getDeclaredMethod(methodName, parameterTypes);

}catch(NoSuchMethodException e)

{//Method 不在当前类定义, 继续向上转型

}//..

}return null;

}privateString toUpperCaseFirstOne(String s)

{//进行字母的ascii编码前移,效率要高于截取字符串进行转换的操作

char[] cs =s.toCharArray();

cs[0] -= 32;returnString.valueOf(cs);

}

4.测试

1.准备请求实体bean

private staticReqSchool makeReq() {

ReqSchool rspSchool= newReqSchool();//学校编号

rspSchool.setNumber("1001");//学校名称

rspSchool.setName("实验小学");//学校省份

rspSchool.setProvince("北京市");//学校地区

rspSchool.setAddress("西城区");//学生班级

rspSchool.setStuclass("高一(2)班");//学生姓名

rspSchool.setStuname("张三");//学生成绩

rspSchool.setStuscore("92");returnrspSchool;

}

2.main方法测试(请求)

public static voidmain(String[] args)

{//定义请求模版

String requestXml="<?xml version=\"1.0\" encoding = \"GBK\"?>\n" +

"\n" +

"

\n" +

"${number}\n" +

"${name}\n" +

"${province}\n" +

"

${address}
\n" +

"\n" +

"

\n" +

"${stuclass}\n" +

"${stuname}\n" +

"${stuscore}\n" +

"\n" +

"";//这里我直接使用构造方法(实际开发应以线程安全的单例模式)

XmlUtil xmlUtil = newXmlUtil();//获取document对象

Document document =xmlUtil.getDocument(requestXml);//获取根元素

Element root =document.getRootElement();//请求实体bean

ReqSchool reqSchool =makeReq();//解析xml,1:表示java2xml

xmlUtil.parseXml(root,"1",reqSchool);//输出请求报文

System.out.println(root.asXML());

}

查看控制台结果:

1f038435d25dd00f8013e33ff11c8ef3.png

3.main方法测试(响应)

public static voidmain(String[] args)

{//定义响应报文

String responseXML="<?xml version=\"1.0\" encoding = \"GBK\"?>\n" +

"\n" +

"

\n" +

"0001\n" +

"xxx实验小学\n" +

"北京市\n" +

"

西城区
\n" +

"\n" +

"

\n" +

" \n" +

" \n" +

" 高三二班\n" +

" 李四\n" +

" 100\n" +

" \n" +

" \n" +

" 高三三班\n" +

" 张三\n" +

" 95\n" +

" \n" +

" \n" +

" 高三四班\n" +

" 王五\n" +

" 0\n" +

" \n" +

" \n" +

"\n" +

"";//这里我直接使用构造方法(实际开发应以线程安全的单例模式)

XmlUtil xmlUtil = newXmlUtil();

Document document=xmlUtil.getDocument(responseXML);

Element rootElement=document.getRootElement();

RspSchool rspSchool= newRspSchool();

xmlUtil.parseXml(rootElement,"2",rspSchool);

System.out.println(rspSchool);

}

控制结果如下:

b50f8a11f23badb53459518c78530f4a.png

五、总结

1.dom4j解析xml的步骤

①获取执行xml的输入流

②创建xml读取对象(SaxReader),用于读取输入流

③通过读取对象(SaxReader)读取xml的输入流,获取文档对象(Document)

④通过文档对象,得到整个文档的 根元素对象(Element)

⑤通过根元素,得到其他层次的所有元素对象

2.反射

反射这里是重中之重,感谢大家的阅读,如有问题,欢迎大家指正~

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐