该帖已经被评为良好帖
|
|||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-10
最后修改:2011-06-22
背景最近公司在大力推进服务化平台,我在中间主要做一个orm框架,解决model和数据源之间的映射问题。这里不选择已有的hibrenate,主要考虑自己公司的一些特殊场景:比如多数据源(数据库,搜索引擎,memcached,nosql等),同时可以 做一些特殊优化,比如多数据源加载时采用并行加载(我之间做的一个工具包 (业务层)异步并行加载技术分析和设计)也不多罗嗦,我主要负责从多个数据源搜集回来的数据,构造成对应的model对象。可以看一下具体的分析过程。
分析每个数据源采集回来的数据可能比较凌乱,有map对象,POJO对象,至于这个映射过程。因为是在老系统上实施,以前都没有service的概念,在数据库的设计会很比较乱,都是来一个需求加几个字段的那一类型。 所以具体data -> model对象的映射过程,就需要额外考虑一些特殊功能。
需求整理:
针对这些需求,回过头看一下现在比较流行的几个处理工具包: cglib , beanutils 。
cglib(之间的一篇分享文档: cglib源码学习交流) : 素以性能著称,主要是BeanCopier,但功能很简单。主要是解决bean之间,同名属性的转化,功能无法满足
beanutils : 功能上支持到是还不错,可以支持处理map,pojo的转化,不同属性类型之间的转化,也可以支持nested子模型的映射。 但无法解决不同名属性之间的映射,json格式类似数据的处理,还有一个就是性能不咋的。根据原先的测试来看,应该是cglib性能的1/1000,相差了3个数量级。
基于如上的需求,以及对现有产品的优缺点分析,所以最后决定我要造一个轮子,刚开始其实还是本着完成项目。但后来发觉写着写着,越来越像那么回事,所以索性剥离了业务部分代码,安心的在底层先实现了一个BeanMapping的轮子。
设计根据上面的需求分析,因为要解决不同属性名自己的映射关系,单纯的依赖bean属性的扫描无法满足。所以需要引入配置文件,定义mapping的映射关系。
整体设计示意图:说明:
Get/Set为整个框架的核心(core)实现,ValueProcess为框架的扩展点,允许自定义一些功能插件,默认已经提供了几个功能处理。
对应的类关系图:Get/Set操作类
ValueProcess设计类: 客户端使用类设计:
几个客户端API说明:
最后定义的配置文件示例: mapping.xml
<bean-mappings xmlns="http://mapping4java.googlecode.com/schema/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mapping4java.googlecode.com/schema/mapping http://mapping4java.googlecode.com/svn/trunk/src/main/resources/META-INF/mapping.xsd"> <!-- (bean-bean) mapping 测试 --> <bean-mapping batch="true" srcClass="com.agapple.mapping.object.SrcMappingObject" targetClass="com.agapple.mapping.object.TargetMappingObject" reversable="true"> <field-mapping srcName="intValue" targetName="intValue" /> <field-mapping targetName="integerValue" script="src.intValue + src.integerValue" /> <!-- 测试script --> <field-mapping srcName="start" targetName="start" /> <field-mapping srcName="name" targetName="targetName" /> <!-- 注意不同名 --> <field-mapping srcName="mapping" targetName="mapping" mapping="true" /> </bean-mapping> <bean-mapping batch="true" srcClass="com.agapple.mapping.object.NestedSrcMappingObject" targetClass="com.agapple.mapping.object.NestedTargetMappingObject" reversable="true"> <field-mapping srcName="name" targetName="name" defaultValue="ljh" /> <!-- 测试default value --> <field-mapping srcName="bigDecimalValue" targetName="value" targetClass="string" defaultValue="10" /> <!-- 测试不同名+不同类型+default value --> </bean-mapping> </bean-mappings> 说明:
几点说明:
使用例子
BeanMapping列子: BeanMappingTest.java public BeanMapping srcMapping = BeanMapping.create(SrcMappingObject.class, TargetMappingObject.class); public BeanMapping targetMapping = BeanMapping.create(TargetMappingObject.class , SrcMappingObject.class); @Test public void testBeanToBean_ok() { SrcMappingObject srcRef = new SrcMappingObject(); srcRef.setIntegerValue(1); srcRef.setIntValue(1); srcRef.setName("ljh"); srcRef.setStart(true); TargetMappingObject targetRef = new TargetMappingObject();// 测试一下mapping到一个Object对象 srcMapping.mapping(srcRef, targetRef); SrcMappingObject newSrcRef = new SrcMappingObject();// 反过来再mapping一次 targetMapping.mapping(targetRef, newSrcRef); }
BeanCopy例子: (发觉和BeanCopier使用比较像) BeanCopyTest.java public BeanCopy srcCopyer = BeanCopy.create(SrcMappingObject.class, TargetMappingObject.class); public BeanCopy targetCopyer = BeanCopy.create(TargetMappingObject.class , SrcMappingObject.class); @Test public void testCopy_ok() { SrcMappingObject srcRef = new SrcMappingObject(); srcRef.setIntegerValue(1); srcRef.setIntValue(1); srcRef.setName("ljh"); srcRef.setStart(true); TargetMappingObject targetRef = new TargetMappingObject();// 测试一下mapping到一个Object对象 srcCopyer.copy(srcRef, targetRef); SrcMappingObject newSrcRef = new SrcMappingObject();// 反过来再mapping一次 targetCopyer.copy(targetRef, newSrcRef); }
BeanMap例子: (支持bean<->map的转化处理) BeanMapTest.java public BeanMap beanMap = BeanMap.create(SrcMappingObject.class); @Test public void testDescribe_Populate_ok() { SrcMappingObject srcRef = new SrcMappingObject(); srcRef.setIntegerValue(1); srcRef.setIntValue(1); srcRef.setName("ljh"); srcRef.setStart(true); NestedSrcMappingObject nestedSrcRef = new NestedSrcMappingObject(); nestedSrcRef.setBigDecimalValue(BigDecimal.ONE); srcRef.setMapping(nestedSrcRef); Map map = beanMap.describe(srcRef); SrcMappingObject newSrcRef = new SrcMappingObject();// 反过来再mapping一次 beanMap.populate(newSrcRef, map); // 从map属性设置到bean } 性能测试数据不过前面功能说的如何天花乱坠,整个工具的性能相比也是大家比较会关注的一个点。 我这里大致做了下测试:构造一个CopyBean,基本涵盖了普通类型,对象处理等,进行批量处理 CopyBean :
public class CopyBean { private int intValue; private boolean boolValue; private float floatValue; private double doubleValue; private long longValue; private char charValue; private byte byteValue; private short shortValue; private Integer integerValue; private Boolean boolObjValue; private Float floatObjValue; private Double doubleObjValue; private Long longObjValue; private Short shortObjValue; private Byte byteObjValue; private BigInteger bigIntegerValue; private BigDecimal bigDecimalValue; private String stringValue; } BeanCopy性能测试 对比的内容:
说明: 因为beanUtils,PropertyUtils的性能太差,基本上其他的柱状图都看不清楚。
说明: 排除了PropertyUtils/BeanUtils的性能对比图,发现BeanMapping提供的copy方法可以比直接Method的反射调用略快一点,不过和cglib的BeanCopier还是有点差距,毕竟人家的功能支持的也很简单,很裸露的get/set调用。性能上基本也和直接硬编码写copy操作一样。
具体测试数据:
说明:
最后整个过程,完成功能代码大概只花了一周的时间,但是代码的重构/抽取,性能优化花了我近2周的时间。性能从最初的比BeanUtils慢,逐步的提升到了快几十倍,还是比较有成就感的。
还有一点,就是自己比较满意ValueProcess概念的设计,相比于BeanCopier或者BeanUtils扩展性好多了,比如自身系统的功能:日志记录,默认值,类型转化,script脚本(EL表达式)。都是通过扩展接口实现,也比较方便切换成不同的实现.。
有兴趣的同学,可以看下googlecode上的代码,有问题欢迎联系!
googlecode地址 : http://code.google.com/p/mapping4java
相关功能测试代码:
性能测试代码:
需求搜集结合了dozer的一些特性,也顺便整理了一下自己的后续action的一些功能点,做了适当取舍。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-10
支持符合属性的copy吗?
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-11
当然支持,只不过你得告诉我这是一个需要做深度复制的对象。默认只做第一层复制
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-12
agapple 写道 当然支持,只不过你得告诉我这是一个需要做深度复制的对象。默认只做第一层复制
你们淘宝带代码出来看起来还是蛮开放的。。。不像我们,不管涉不涉及业务,是代码就带不出来。。。 确实插件化的设计还是很赞的,另外用script在xml里面,我们的项目里也有用,感觉表达能力很强,很好。 |
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-12
前排就座,打算好好研究学习下楼主的代码,
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-12
google code 地址错了
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-12
bugu1986 写道 agapple 写道 当然支持,只不过你得告诉我这是一个需要做深度复制的对象。默认只做第一层复制
你们淘宝带代码出来看起来还是蛮开放的。。。不像我们,不管涉不涉及业务,是代码就带不出来。。。 确实插件化的设计还是很赞的,另外用script在xml里面,我们的项目里也有用,感觉表达能力很强,很好。 从哪里看出有淘宝的信息? 呵呵,我非淘宝人员 可以这么说,代码是你自己写的。只不过用在公司的某个项目上而已 我们一般也会要求,涉及公司信息的代码或者信息不能在blog里讨论 |
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-12
neverforget 写道 google code 地址错了
hi,地址没错,可能你得翻个墙了 |
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-13
没看具体实现,楼主可以针对JDK6以上采用Unsafe,如果楼主已经采用了,请忽略。
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||
发表时间:2011-06-13
请问性能测试的部分,是用什么工具做的?
|
|||||||||||||||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||||||||||||||