本文将结合 面向对象设计原则实践来介绍如何将一个Java对象树输出到XML文件模块。

需求

项目功能:查询数据并生成XML文件然后上传至指定服务器
本模块功能:实现Java对象树输出到 XML文件。

要求
1. 支持对象及属性的扩展,而XML输出模块代码基本不变。
2. 考虑到内存压力,要求该模块实现以追加方式操作XML文件。

输入
Java对象树(提供一个类以方法,作为该对象的操作入口)

输出
XML文档

数值传输对象( DTO,Data Transfer Object),仅包含属性和setter/getter方法。如例1所示。属性的型别基本固定(例如String, int,boolean,List等),对于List的属性,可能包含另一个Java Object(如例3所示)。XML输出格式很简单,除去头部信息之外基本是attribute+value。例2是例1对象XML输出片段。

例1

java 代码

1. public class
2. private
3. private
4. private
5.    
6. public void
7. this.fromNo = fromNo;    
8.     }    
9.       
10. public
11. return
12.      }    
13.            
14. public void
15. this.toNo = toNo;    
16.      }    
17.           
18. public
19. return
20.      }    
21. }

例2


xml 代码

1. <fromNo>DLS</fromNo>
2. <toNo>BJS</toNo>

例3


java 代码

1. public class CarrierFlight implements
2. /** 
3.      * 
4.      */
5. private static final long
6. private List apFlightNoList = new ArrayList(); //apFlightNoList存储FromTo类型的对象  
7. private List exFlightNoList = new ArrayList();  //同上  
8. private
9. private
10.         
11. public
12.         
13. /** 
14.      * @param apFlightNoList 
15.      */
16. public void
17. this.apFlightNoList = apFlightNoList;    
18.     }    
19.         
20. /** 
21.      * @return 
22.      */
23. public
24. return
25.     }    
26.     
27. /** 
28.      * @param apFlightNos 
29.      */
30. public void
31. if(!ArraysUtil.foreachable(apFlightNos))    
32. return;    
33. for(int i=0;i < apFlightNos.length;i++)  
34. this.apFlightNoList.add(apFlightNos[i];    
35.     }    
36.         
37. /** 
38.       * @param exFlightNoList 
39.       */
40. public void
41. this.exFlightNoList = exFlightNoList;    
42.     }    
43.         
44. /** 
45.      * @return 
46.      */
47. public
48. return
49.     }    
50.         
51. /** 
52.      * @param exFlightNos 
53.      */
54. public void
55. if(!ArraysUtil.foreachable(exFlightNos))    
56. return;    
57. for(int i=0;i < exFlightNos.length;i++)  
58. this.exFlightNoList.add(exFlightNos[i];    
59.     }    
60.         
61. /** 
62.      * @param carrierCode 
63.      */
64. public void
65. this.carrierCode = carrierCode;    
66.     }    
67.         
68. /** 
69.      * @return 
70.      */
71. public
72. return this.carrierCode;    
73.     }    
74.     
75. /** 
76.      * @param rbd 
77.      */
78. public void
79. this.rbd = rbd;    
80.    }    
81.   
82. /** 
83.      * @return  
84.      */
85. public
86. return this.rbd;    
87.     }    
88. }


分析


先介绍一下XML输出模块的旧版本。它直接处理判断对象的类型,并显示获取其getter方法,得到属性的值后操作XML文档(即dom4j的Document)。如下所示

java 代码

1. Object obj = getFromToObject();      
2.            
3. if(fromTo instanceof
4.     FromTo fromto = (FromTo)obj;      
5.     String fromNo = fromto.getFromNo();      
6.     String toNo = fromto.getToNo();      
7. if(fromNo!=null && fromNo.length()>0){      
8. "<fromNo>"+fromNo+"</fromNo>");      
9.     }      
10. if(toNo!=null && toNo.length()>0){      
11. "<toNo>"+toNo+"</toNo>");      
12.     }    
13. }      
14. //....



面向对象设计两个重要原则:单一职责原则(SRP)和开放-闭合原则(OCP)。SRP要求对每个类仅有一个引起它变化的原因。OCP要求对扩展开放,对修改封闭。<o:p></o:p>

上述代码很容易理解,但它充满臭味:不必要的重复和脆弱性(想象如何把例3展示的CarrierFlight输出到XML)。引起上述代码发生变化的原因很多:Java对象树中某个对象的属性或者XML输出格式改变。同时,缺乏抽象导致该类很难扩展(例如增加对新类型的Object的支持而代码不改变),每次修改都必须改动同一个类。

<o:p></o:p>
<o:p>遵循SRP和OCP,需要:</o:p>
<o:p>1.把XML输出和对象解析过程分离</o:p>
<o:p>2.把频繁变化的部分抽象 
 </o:p>


设计


对象解析
如说封装对象及属性的变化。数值对象有属性组成(以及setter/getter方法,此处忽略);Property(及属性)由类型(已封装成Type类,它将常用的类型定义成类并将静态实例暴露给外界),名称和Alias组成。对象树实际上是数据结构中的树。属性就是叶子。List型属性是可解析的,因此有字节点;非List型属性是末级节点。是否可解析可通过调用listable方法判断。


Property类实现了属性初始化(加载所有属性并生成相应对象供解析时使用)。利用反射机制可获得每个属性的的getter方法并在对象上调用该方法以取得其值。遍历所有节点可构造成一棵Element(稍后还会讲到该接口)树。


XML输出


dom4j是一个开源的XML工具,它支持XML解析和输出。但输出XML的过程采用内存构造Document方式。输出时调用Document.write方法写文件。前面已经提到,由于对象树非常庞大,一次构造完整的Document对象将导致OutOfMemoryError错误。如果能够实现以Append操作XML文件方式替换前面提到的方式,将会避免该问题出现。


Element接口定义了XML元素这个概念。它支持设置元素名称,文本和IO输出操作。XMLStream实现了Element接口,可以表示一个XMl元素并能输出到指定设备。XMLWriter实现了对Element输出到XML的过程。