在作业务的时候,咱们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是相似的,可是咱们很不喜欢写不少冗长的b.setF1(a.getF1())这样的代码,因而咱们须要BeanCopier来帮助咱们。html
BeanCopier
其实已经有不少开源版本,例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils甚至是Cglib都提供了这样的功能。在比较这些工具以前,我想先提提我对BeanCopier的一些要求。java
BeanCopier是一个很经常使用的操做,若是是一个批量的请求,就更加明显了。使用效率过低的库不太划算,我对这些工具作了一个对比:Copy一个简单Bean 1,000,000次,计算总耗时(测试代码在这里)。比较结果以下:git
1,000,000 round jdk set/get takes 17ms cglib takes 117ms jodd takes 5309ms dozer mapper takes 2336ms apche beanutils takes 6264ms
其中jdk的直接写set/get是最快的,因此在性能要求高的场景下却是不妨本身写。另外这样写也是对重构比较友好,这是其余几个工具都作不到的。github
其次是用了字节码生成的cglib,而后将其余的库远远甩在后面。其余的库性能相差不大,大约1000次拷贝会消耗数毫秒时间,对于性能敏感的应用,特别是一些批量请求,消耗仍是比较大的。spring
其实Bean Copy能够扩展到更通常的状况:咱们须要对两个相似的Bean作转换,输入是一个Bean,输出是另一个相似的Bean。这种逻辑里,除了简单的字段拷贝,可能也会有一些计算逻辑,甚至还会依赖一些外部数据源,而咱们还但愿最好把转换的逻辑都放在一块儿,同时也起到规范业务的做用。数据库
DozerMapper
在这条路上走的很远。它经过XML/API/Annotation的方式,支持简单形式的转换、映射,从而更好的处理一些字段不同的状况,用意就是一个Mapper搞定一切。例以下面的例子,能够将不一样名称的字段进行映射。apache
<?xml version="1.0" encoding="UTF-8"?> <mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd"> <mapping> <class-a>org.dozer.vo.TestObject</class-a> <class-b>org.dozer.vo.TestObjectPrime</class-b> <field> <a>one</a> <b>onePrime</b> </field> </mapping> <mapping wildcard="false"> <class-a>org.dozer.vo.TestObjectFoo</class-a> <class-b>org.dozer.vo.TestObjectFooPrime</class-b> <field> <a>oneFoo</a> <b>oneFooPrime</b> </field> </mapping> </mappings>
可是,假设咱们的场景不是须要整合不少项目,而是本身制定规范和数据模型,这时咱们真的须要这样的转换么?我认为一开始就应该把相同的字段给予相同的名字,这样不管是对于理解、后续维护都会方便不少。即便这种不一样名的状况存在,咱们也不该该提倡。因此花这么大的力气去作字段的映射,增长了复杂度,我认为并不划算。这个时候,咱们须要的是仅仅对同名字段进行拷贝,其余属性交由手动处理。app
至此,一个BeanCopier就大致成型了:ide
<!-- lang: java --> public class BeanCopier<F,T> { private net.sf.cglib.beans.BeanCopier beanCopier; protected net.sf.cglib.beans.BeanCopier getBeanCopier() { return beanCopier; } protected void init(){ this.beanCopier = net.sf.cglib.beans.BeanCopier.create(sourceClass, targetClass, false); } private Class<T> targetClass; private Class<F> sourceClass; protected Class<T> getTargetClass() { return targetClass; } protected Class<F> getSourceClass() { return sourceClass; } public void setTargetClass(Class<T> targetClass) { this.targetClass = targetClass; } public void setSourceClass(Class<F> sourceClass) { this.sourceClass = sourceClass; } public T afterCopy(F source, T target){ return target; } public T copy(F input) { try { T o = targetClass.newInstance(); beanCopier.copy(input, o, null); return afterCopy(input, o); } catch (Exception e) { throw new RuntimeException("create object fail, class:" + targetClass.getName() + " ", e); } } @Override public T apply(F input) { return copy(input); }
}工具
另外,不少状况下,咱们不止是对字段值进行拷贝,还会有一些数据转换的须要。例如:将Entity的瘦模型中关联的一些数据,从简单的数据库关联外键变为一个完整的Entity,最后再整合成一个DTO。
这种状况下,咱们的BeanCopyier还须要一些外部数据。在Spring中,咱们会但愿它去依赖DAO或者外部Service之类的Bean。因而咱们还能够用Spring来配置它。
@Service public class A2BBeanCopier extends BeanCopier<A,B> { @PostConstruct public void init(){ setSourceClass(A.class); setTargetClass(B.class); super.init(); } @Override public B afterCopy(A source, B target) { target.setF5("aaa"); //Call some service return target; } }
最后,项目我放到了oscgit上:http://git.oschina.net/flashsword20/abc。