请开始使用Orika
如果你曾经做个几个中等规模的Java企业级应用,那么,我假设你一定会与至少一个外部服务集成,当前我所在的项目就有至少一个这样的服务。这个服务是比较典型的基于SOAP的WebService,它对外提供WSDL和XSD文件,作为客户端,你可以使用像CXF这样的工具,自动生成该服务的Domain Object(DO)和调用该服务的工具类。直到这一步,一切都很好,你不需要做太多的事情,仅仅是使用几个工具就已经可以和这个服务通信了。
然而,故事才刚刚开始,通常,服务提供的DO和客户端的业务并不能完全匹配,而我们的应用为了做到Business Relevant,DO都是和业务密切相关的。当面对这样的局面时,我们不可避免的需要做不同DO之间的相互映射(Mapping)。这种映射通常来说都是双向的,这分别代表了发送请求和接受响应两个部分。
如果你面对的差异是非常小的,或者两边DO的字段都不是很多,那么,这并不是什么负担,自己写几个Mapper就可以解决问题了。然而,如果你面对的是几十个甚至上百个需要Mapping的对象,那么,这个工作量可是不小的,同时也是非常tedious的,通常就需要借助工具的帮助。
Dozer是解决方案之一,它使用XML配置不同对象和字段之间的映射关系。同时,它也支持自定义转换规则。然而,作为程序员,或者就我个人而言,我不太喜欢XML。因此,我更喜欢Spring的Annotation而非Configuration文件。
Orika是另外一种符合我口味的解决方案。它提供完全的Java API,并提供Fluent API。具体教程可以看其官方的文档,我就不说了。下面就介绍我这两天使用Orika的一些感受。
使用ClassMap分离责任
构建ClassMap是非常简单的事情:
ClassMap<A, B> aAndBMap = factory.classMap(A.class, B.class).toClassMap();
factory.registerClassMap(aAndBMap);
其中,factory是MapperFactory对象,上面的代码创建了一个A与B的ClassMap并将这个ClassMap注册到factory中。注意,这里我并没有说是A到B的映射,原因是在Orika里定义的ClassMap默认是双向(bi-directional)映射关系。一旦注册一个ClassMap,Orika就会根据源对象的类型以及目标对象的类型是否匹配某个ClassMap来判断是否需要调用一个已经注册的ClassMap。也就是说如下两种种情况都会调用这个ClassMap:
// case 1
MapperFacade mapper = factory.getMapperFacade();
A a = new A();
B b = mapper.map(A, B.class); // will call aAndBMap
// case 2
public class C {
private A a;
// omit getter/setter
}
public class D {
private B b;
// omit getter/setter
}
factory.classMap(C.class, D.class)
.field("a", "b") // will call aAndBMap
.register();
MapperFacade mapper = factory.getMapperFacade();
C c = new C();
D d = mapper.map(c, D.class);
如果仔细看第二个例子,我们可以发现,这个特性非常适合解决对象层级非常深的映射。例如:
public class E {
private C c;
// omit getter/setter
}
public class F {
private D d;
// omit getter/setter
}
factory.classMap(E.class, F.class)
.field("c", "d") // delegate to aAndBMap
.register();
对于E和F的映射,我们只需要配置C和D关系,而不再需要深入配置C.a与D.b的关系,因为这个关系将由aAndBMap解决。因此,这样有利于将映射解耦,从而分离责任。
使用ObjectFactory创建对象并设置默认值
ObjectFactory是另一个非常好的特性。比如,我们在做映射的过程中,有些字段是不需要映射的,然后他们却有默认值,那么用ObjectFactory非常合适。
public class A {
private String fieldA;
// omit getter/setter
}
public class B {
private String version;
private String fieldB;
// omit getter/setter
}
public BObjectFactory implements ObjectFactory<B> {
@Override
public B create(Object source, MappingContext mappingContext) {
B b = new B();
b.setVersion("1.0");
return b;
}
}
// register BObjectFactory into factory
factory.registerObjectFactory(new BObjectFactory(), TypeFactory.valueOf(B.class));
这样,Orika在映射过程中需要创建B对象时,它会调用BObjectFactory的create方法来创建,当然,B的实例就已经有了version的默认值。
使用多种字段映射规则
在配置不同字段之间的映射关系时,Orika提供了非常灵活的方式帮助你定义这个关系。例如:
factory.classMap(A.class, B.class)
.field("fieldA", "fieldB") // bi-directional mapping
.fieldAToB("fieldA", "fieldB") // single directional mapping
.fieldMap("fieldA", "fieldB").aToB().add() // same as above, single directional mapping
.fieldMap("fieldA", "fieldB").convert("some_converter").add() // customize mapping converter
.register();
并且,对于Converter,你可以选择是单向(继承CustomConverter)还是双向(继承BiDirectionalConverter)。
总结
这里仅仅列举了一小部分Orika的特性,还有很多其他的特性可以通过查看官方文档获得。我们在项目中已经使用了Orika,而且,就实现来讲,仅仅一个下午就基本实现一个复杂外部服务的Mapping,生产率还是很可观的。有兴趣的试试吧。