在将一个对象序列化过程中,引用对象的处理是一个常见问题。
随着新版本XBlink的即将推出,这个老大难问题就需要慎重解决。
在0.7.0版中,引用虽然已经能够正确处理,但是实现方式是采用了对象出现先后顺序为标记,采用数字的方式来实现引用。
例如 A包含B,B包含C,C包含A,C也包含B。生成的XML如下:
<A>
<B>
<C>
<A ref="1"/><!-- A是第一个序列化的对象,ID设置为1 -->
<B ref="2"/><!-- B是第二个序列化的对象,ID设置为2 -->
</C>
</B>
</A>
这个摸样虽然没错,但是在难以阅读。(毕竟序列化为XML的优点之一就是其可读性好,如果不好,那还不如用其他格式)
新版本的开发决定彻底解决这个问题,让引用路径可以清楚表示出其对象位置。
思考后,解决方式就是生成常见的两种路径:相对路径 绝对路径
在这个实现过程中,看了下Fastjson与XStream的实现方式,发现以下几点问题:
首先说说温少的Fastjson的实现方式
这里的路径感觉有稍许混乱,有的是相对路径,例如使用..来引用上级对象,但也有几个特殊符号,使得有的路径又变成了绝对路径,例如使用对于根节点使用$来表示,自身使用@来表示。
个人认为这样会产生一个问题,例如上面那个ABC的例子,可以有两种表现形式
<A>
<B>
<C>
<A ref="$"/>
<B ref="../.."/>
</C>
</B>
</A>
<!-- 下面这种也是正确的 -->
<A>
<B>
<C>
<A ref="../../.."/>
<B ref="$.B"/>
</C>
</B>
</A>
像例子中关于B节点就有两种不同的表示方式,一个文件中会混杂这两种格式,这就是个人觉得有稍许混乱的原因。
代码的话查看com.alibaba.fastjson.serializer.SerialContext
还有com.alibaba.fastjson.serializer.JSONSerializer的writeReference方法
里面实现是优先判断是否属于那几个特殊符号使用范围,不行再计算路径。
路径是通过一个链表形式存放的,不断查找父节点,拼装路径,具体的还是看源码吧。
再谈谈XStream的实现方式
XStream采用了常见的相对路径来表示位置关系(个人比较喜欢这个)。
代码看com.thoughtworks.xstream.io.path包。
简单分析下几个类的作用:
Path 就是封装路径用的对象,里面有着计算相对路径的方法。
PathTracker 名字都告诉你了,Tracker!里面有一个栈,进去一个节点,把节点名称压入栈中,退出这个节点,弾栈,也就是说记录当前路径信息,通过它可以获得你当前所在节点的绝对路径。
PathTrackingWriter 一个包装类,里面实际工作的是HierarchicalStreamWriter也就是用来写XML文件的,在写节点开始标签时,调用PathTracker记录那个节点名称,写节点结束标签时,再调用PathTracker 删掉那个节点名称。
PathTrackingReader 与PathTrackingWriter 功能类似,反序列时用的。
最后说下XBlink的实现思路,其实跟XStream差不多,基本就是参考它的,做了一些简化与改进。
详情见org.xblink.core.path包(里面几个类也是借鉴了XStream,哈哈,谁叫XStream是XBlink的老师来)
讲下思路,首先在开始写XML时,生成一个PathTracker实例。
实现一个引用缓存,例如一个key是对象,value是绝对路径的Map,用来记录对象与其绝对路径的关系。
首先一个对象序列化,先去这个缓存查看一下,是否已经存在了,如果没有,那就开始写一个节点,记录下这个节点名称,放入PathTracker中。当你发现有个节点是可以被引用的(可能被引用,后面是不是真的被引用还不知道),就通过PathTracker生成当前这个节点的绝对路径,将这个类与路径记录引用缓存中,然后序列化下一个对象。
当发现要某个对象是要在缓存中存在的了,那就生成当前路径(也是一个绝对路径),通过一个算法,将此路径与缓存中那个对象的绝对路径进行比较,生成相对路径。
在代码上如果变通一下的话,你可以设置一个开关,关于引用是采用相对路径还是绝对路径(这两者你都可以拿到)
这样不但你让用户多了一个选择(更人性化),如果使用绝对路径,还能减少一次计算路径的开销,提高了效率。
以上就是一些个人想法,欢迎大家提出意见探讨。