关于BeanCopier的一些思考

来源:互联网 发布:ssc ultimate aero数据 编辑:程序博客网 时间:2024/05/17 09:13



在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。

在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。

BeanCopier其实已经有很多开源版本,例如DozerMapper、Apache BeanUtils、Spring、Jodd BeanUtils甚至是Cglib都提供了这样的功能。在比较这些工具之前,我想先提提我对BeanCopier的一些要求。

1. 性能

BeanCopier是一个很常用的操作,如果是一个批量的请求,就更加明显了。使用效率太低的库不太划算,我对这些工具做了一个对比:Copy一个简单Bean 1,000,000次,计算总耗时(测试代码在这里)。比较结果如下:

1
2
3
4
5
6
1,000,000round
jdk set/get takes 17ms
cglib takes 117ms
jodd takes 5309ms
dozer mapper takes 2336ms
apche beanutils takes 6264ms

其中jdk的直接写set/get是最快的,所以在性能要求高的场景下倒是不妨自己写。另外这样写也是对重构比较友好,这是其他几个工具都做不到的。

其次是用了字节码生成的cglib,然后将其他的库远远甩在后面。其他的库性能相差不大,大约1000次拷贝会消耗数毫秒时间,对于性能敏感的应用,特别是一些批量请求,消耗还是比较大的。

2. 内聚性

其实Bean Copy可以扩展到更一般的情况:我们需要对两个类似的Bean做转换,输入是一个Bean,输出是另外一个类似的Bean。这种逻辑里,除了简单的字段拷贝,可能也会有一些计算逻辑,甚至还会依赖一些外部数据源,而我们还希望最好把转换的逻辑都放在一起,同时也起到规范业务的作用。

DozerMapper在这条路上走的很远。它通过XML/API/Annotation的方式,支持简单形式的转换、映射,从而更好的处理一些字段不一样的情况,用意就是一个Mapper搞定一切。例如下面的例子,可以将不同名称的字段进行映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xmlversion="1.0"encoding="UTF-8"?>
<mappingsxmlns="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
 
  <mappingwildcard="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>

但是,假设我们的场景不是需要整合很多项目,而是自己制定规范和数据模型,这时我们真的需要这样的转换么?我认为一开始就应该把相同的字段给予相同的名字,这样无论是对于理解、后续维护都会方便很多。即使这种不同名的情况存在,我们也不应该提倡。所以花这么大的力气去做字段的映射,增加了复杂度,我认为并不划算。这个时候,我们需要的是仅仅对同名字段进行拷贝,其他属性交由手动处理

至此,一个BeanCopier就大体成型了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
publicclass BeanCopier<F,T> {
 
privatenet.sf.cglib.beans.BeanCopier beanCopier;
 
protectednet.sf.cglib.beans.BeanCopier getBeanCopier() {
    returnbeanCopier;
}
 
protectedvoid init(){
    this.beanCopier = net.sf.cglib.beans.BeanCopier.create(sourceClass, targetClass, false);
}
 
privateClass<T> targetClass;
 
privateClass<F> sourceClass;
 
protectedClass<T> getTargetClass() {
    returntargetClass;
}
 
protectedClass<F> getSourceClass() {
    returnsourceClass;
}
 
publicvoid setTargetClass(Class<T> targetClass) {
    this.targetClass = targetClass;
}
 
publicvoid setSourceClass(Class<F> sourceClass) {
    this.sourceClass = sourceClass;
}
 
publicT afterCopy(F source, T target){
    returntarget;
}
 
publicT copy(F input) {
    try{
        T o = targetClass.newInstance();
        beanCopier.copy(input, o, null);
        returnafterCopy(input, o);
    }catch(Exception e) {
        thrownew RuntimeException("create object fail, class:" + targetClass.getName() + " ", e);
    }
}
 
@Override
publicT apply(F input) {
    returncopy(input);
}
}

 

另外,很多情况下,我们不止是对字段值进行拷贝,还会有一些数据转换的需要。例如:将Entity的瘦模型中关联的一些数据,从简单的数据库关联外键变为一个完整的Entity,最后再整合成一个DTO。

这种情况下,我们的BeanCopyier还需要一些外部数据。在Spring中,我们会希望它去依赖DAO或者外部Service之类的Bean。于是我们还可以用Spring来配置它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
publicclass A2BBeanCopier extendsBeanCopier<A,B> {
 
    @PostConstruct
    publicvoid init(){
        setSourceClass(A.class);
        setTargetClass(B.class);
        super.init();
    }
 
    @Override
    publicB afterCopy(A source, B target) {
        target.setF5("aaa");
        //Call some service
        returntarget;
    }
}

原创粉丝点击