背景

在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文将讲述上面几个工具的使用、性能对比及原理分析。

性能分析

其实这几个工具要做的事情很简单,而且在使用上也是类似的,所以我觉得先给大家看看性能分析的对比结果,让大家有一个大概的认识。我是使用JMH来做性能分析的,代码如下:
要复制的对象比较简单,包含了一些基本类型;有一次warmup,因为一些工具是需要“预编译”和做缓存的,这样做对比才会比较客观;分别复制1000、10000、100000个对象,这是比较常用数量级了吧。

  1. @BenchmarkMode(Mode.AverageTime)
  2. @OutputTimeUnit(TimeUnit.MICROSECONDS)
  3. @Fork(1)
  4. @Warmup(iterations = 1)
  5. @State(Scope.Benchmark)
  6. public class BeanMapperBenchmark {
  7. @Param({"1000", "10000", "100000"})
  8. private int times;
  9. private int time;
  10. private static MapperFactory mapperFactory;
  11. private static Mapper mapper;
  12. static {
  13. mapperFactory = new DefaultMapperFactory.Builder().build();
  14. mapperFactory.classMap(SourceVO.class, TargetVO.class)
  15. .byDefault()
  16. .register();
  17. mapper = DozerBeanMapperBuilder.create()
  18. .withMappingBuilder(new BeanMappingBuilder() {
  19. @Override
  20. protected void configure() {
  21. mapping(SourceVO.class, TargetVO.class)
  22. .fields("fullName", "name")
  23. .exclude("in");
  24. }
  25. }).build();
  26. }
  27. public static void main(String[] args) throws Exception {
  28. Options options = new OptionsBuilder()
  29. .include(BeanMapperBenchmark.class.getName()).measurementIterations(3)
  30. .build();
  31. new Runner(options).run();
  32. }
  33. @Setup
  34. public void prepare() {
  35. this.time = times;
  36. }
  37. @Benchmark
  38. public void springBeanUtilTest(){
  39. SourceVO sourceVO = getSourceVO();
  40. for(int i = 0; i < time; i++){
  41. TargetVO targetVO = new TargetVO();
  42. BeanUtils.copyProperties(sourceVO, targetVO);
  43. }
  44. }
  45. @Benchmark
  46. public void apacheBeanUtilTest() throws Exception{
  47. SourceVO sourceVO = getSourceVO();
  48. for(int i = 0; i < time; i++){
  49. TargetVO targetVO = new TargetVO();
  50. org.apache.commons.beanutils.BeanUtils.copyProperties(targetVO, sourceVO);
  51. }
  52. }
  53. @Benchmark
  54. public void beanCopierTest(){
  55. SourceVO sourceVO = getSourceVO();
  56. for(int i = 0; i < time; i++){
  57. TargetVO targetVO = new TargetVO();
  58. BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);
  59. bc.copy(sourceVO, targetVO, null);
  60. }
  61. }
  62. @Benchmark
  63. public void dozerTest(){
  64. SourceVO sourceVO = getSourceVO();
  65. for(int i = 0; i < time; i++){
  66. TargetVO map = mapper.map(sourceVO, TargetVO.class);
  67. }
  68. }
  69. @Benchmark
  70. public void orikaTest(){
  71. SourceVO sourceVO = getSourceVO();
  72. for(int i = 0; i < time; i++){
  73. MapperFacade mapper = mapperFactory.getMapperFacade();
  74. TargetVO map = mapper.map(sourceVO, TargetVO.class);
  75. }
  76. }
  77. private SourceVO getSourceVO(){
  78. SourceVO sourceVO = new SourceVO();
  79. sourceVO.setP1(1);
  80. sourceVO.setP2(2L);
  81. sourceVO.setP3(new Integer(3).byteValue());
  82. sourceVO.setDate1(new Date());
  83. sourceVO.setPattr1("1");
  84. sourceVO.setIn(new SourceVO.Inner(1));
  85. sourceVO.setFullName("alben");
  86. return sourceVO;
  87. }
  88. }

在我macbook下运行后的结果如下:
BeanUtils、BeanCopier、Dozer、Orika 哪个性能最强? - 图1
Score表示的是平均运行时间,单位是微秒。从执行效率来看,可以看出 beanCopier > orika > springBeanUtil > dozer > apacheBeanUtil。这样的结果跟它们各自的实现原理有很大的关系,
下面将详细每个工具的使用及实现原理。

Spring的BeanUtils

使用

这个工具可能是大家日常使用最多的,因为是Spring自带的,使用也简单:BeanUtils.copyProperties(sourceVO, targetVO);

原理

Spring BeanUtils的实现原理也比较简答,就是通过Java的Introspector获取到两个类的PropertyDescriptor,对比两个属性具有相同的名字和类型,如果是,则进行赋值(通过ReadMethod获取值,通过WriteMethod赋值),否则忽略。
为了提高性能Spring对BeanInfo和PropertyDescriptor进行了缓存。
(源码基于:org.springframework:spring-beans:4.3.9.RELEASE)

  1. /**
  2. * Copy the property values of the given source bean into the given target bean.
  3. * <p>Note: The source and target classes do not have to match or even be derived
  4. * from each other, as long as the properties match. Any bean properties that the
  5. * source bean exposes but the target bean does not will silently be ignored.
  6. * @param source the source bean
  7. * @param target the target bean
  8. * @param editable the class (or interface) to restrict property setting to
  9. * @param ignoreProperties array of property names to ignore
  10. * @throws BeansException if the copying failed
  11. * @see BeanWrapper
  12. */
  13. private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
  14. throws BeansException {
  15. Assert.notNull(source, "Source must not be null");
  16. Assert.notNull(target, "Target must not be null");
  17. Class<?> actualEditable = target.getClass();
  18. if (editable != null) {
  19. if (!editable.isInstance(target)) {
  20. throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
  21. "] not assignable to Editable class [" + editable.getName() + "]");
  22. }
  23. actualEditable = editable;
  24. }
  25. //获取target类的属性(有缓存)
  26. PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
  27. List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
  28. for (PropertyDescriptor targetPd : targetPds) {
  29. Method writeMethod = targetPd.getWriteMethod();
  30. if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
  31. //获取source类的属性(有缓存)
  32. PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
  33. if (sourcePd != null) {
  34. Method readMethod = sourcePd.getReadMethod();
  35. if (readMethod != null &&
  36. //判断target的setter方法入参和source的getter方法返回类型是否一致
  37. ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
  38. try {
  39. if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
  40. readMethod.setAccessible(true);
  41. }
  42. //获取源值
  43. Object value = readMethod.invoke(source);
  44. if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
  45. writeMethod.setAccessible(true);
  46. }
  47. //赋值到target
  48. writeMethod.invoke(target, value);
  49. }
  50. catch (Throwable ex) {
  51. throw new FatalBeanException(
  52. "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }

小结

Spring BeanUtils的实现就是这么简洁,这也是它性能比较高的原因。
不过,过于简洁就失去了灵活性和可扩展性了,Spring BeanUtils的使用限制也比较明显,要求类属性的名字和类型一致,这点在使用时要注意。

Apache的BeanUtils

使用

Apache的BeanUtils和Spring的BeanUtils的使用是一样的:
BeanUtils.copyProperties(targetVO, sourceVO);

要注意,source和target的入参位置不同。

原理

Apache的BeanUtils的实现原理跟Spring的BeanUtils一样,也是主要通过Java的Introspector机制获取到类的属性来进行赋值操作,对BeanInfo和PropertyDescriptor同样有缓存,但是Apache BeanUtils加了一些不那么使用的特性(包括支持Map类型、支持自定义的DynaBean类型、支持属性名的表达式等等)在里面,使得性能相对Spring的BeanUtils来说有所下降。
(源码基于:commons-beanutils:commons-beanutils:1.9.3)

  1. public void copyProperties(final Object dest, final Object orig)
  2. throws IllegalAccessException, InvocationTargetException {
  3. if (dest == null) {
  4. throw new IllegalArgumentException
  5. ("No destination bean specified");
  6. }
  7. if (orig == null) {
  8. throw new IllegalArgumentException("No origin bean specified");
  9. }
  10. if (log.isDebugEnabled()) {
  11. log.debug("BeanUtils.copyProperties(" + dest + ", " +
  12. orig + ")");
  13. }
  14. // Apache Common自定义的DynaBean
  15. if (orig instanceof DynaBean) {
  16. final DynaProperty[] origDescriptors =
  17. ((DynaBean) orig).getDynaClass().getDynaProperties();
  18. for (DynaProperty origDescriptor : origDescriptors) {
  19. final String name = origDescriptor.getName();
  20. // Need to check isReadable() for WrapDynaBean
  21. // (see Jira issue# BEANUTILS-61)
  22. if (getPropertyUtils().isReadable(orig, name) &&
  23. getPropertyUtils().isWriteable(dest, name)) {
  24. final Object value = ((DynaBean) orig).get(name);
  25. copyProperty(dest, name, value);
  26. }
  27. }
  28. // Map类型
  29. } else if (orig instanceof Map) {
  30. @SuppressWarnings("unchecked")
  31. final
  32. // Map properties are always of type <String, Object>
  33. Map<String, Object> propMap = (Map<String, Object>) orig;
  34. for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
  35. final String name = entry.getKey();
  36. if (getPropertyUtils().isWriteable(dest, name)) {
  37. copyProperty(dest, name, entry.getValue());
  38. }
  39. }
  40. // 标准的JavaBean
  41. } else {
  42. final PropertyDescriptor[] origDescriptors =
  43. //获取PropertyDescriptor
  44. getPropertyUtils().getPropertyDescriptors(orig);
  45. for (PropertyDescriptor origDescriptor : origDescriptors) {
  46. final String name = origDescriptor.getName();
  47. if ("class".equals(name)) {
  48. continue; // No point in trying to set an object's class
  49. }
  50. //是否可读和可写
  51. if (getPropertyUtils().isReadable(orig, name) &&
  52. getPropertyUtils().isWriteable(dest, name)) {
  53. try {
  54. //获取源值
  55. final Object value =
  56. getPropertyUtils().getSimpleProperty(orig, name);
  57. //赋值操作
  58. copyProperty(dest, name, value);
  59. } catch (final NoSuchMethodException e) {
  60. // Should not happen
  61. }
  62. }
  63. }
  64. }
  65. }

小结

Apache BeanUtils的实现跟Spring BeanUtils总体上类似,但是性能却低很多,这个可以从上面性能比较看出来。阿里的Java规范是不建议使用的。
另外,关注公众号Java核心技术,在后台回复:手册,可以获取最新阿里的 Java 开发手册。

BeanCopier

使用

BeanCopier在cglib包里,它的使用也比较简单:

  1. @Test
  2. public void beanCopierSimpleTest() {
  3. SourceVO sourceVO = getSourceVO();
  4. log.info("source={}", GsonUtil.toJson(sourceVO));
  5. TargetVO targetVO = new TargetVO();
  6. BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);
  7. bc.copy(sourceVO, targetVO, null);
  8. log.info("target={}", GsonUtil.toJson(targetVO));
  9. }

只需要预先定义好要转换的source类和target类就好了,可以选择是否使用Converter,这个下面会说到。
在上面的性能测试中,BeanCopier是所有中表现最好的,那么我们分析一下它的实现原理。

原理

BeanCopier的实现原理跟BeanUtils截然不同,它不是利用反射对属性进行赋值,而是直接使用cglib来生成带有的get/set方法的class类,然后执行。由于是直接生成字节码执行,所以BeanCopier的性能接近手写
get/set。
BeanCopier.create方法

  1. public static BeanCopier create(Class source, Class target, boolean useConverter) {
  2. Generator gen = new Generator();
  3. gen.setSource(source);
  4. gen.setTarget(target);
  5. gen.setUseConverter(useConverter);
  6. return gen.create();
  7. }
  8. public BeanCopier create() {
  9. Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
  10. return (BeanCopier)super.create(key);
  11. }

这里的意思是用KEY_FACTORY创建一个BeanCopier出来,然后调用create方法来生成字节码。
KEY_FACTORY其实就是用cglib通过BeanCopierKey接口生成出来的一个类

  1. private static final BeanCopierKey KEY_FACTORY = (BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
  2. interface BeanCopierKey {
  3. public Object newInstance(String source, String target, boolean useConverter);
  4. }

通过设置

  1. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "path");

可以让cglib输出生成类的class文件,我们可以反编译看看里面的代码
下面是KEY_FACTORY的类

  1. public class BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd extends KeyFactory implements BeanCopierKey {
  2. private final String FIELD_0;
  3. private final String FIELD_1;
  4. private final boolean FIELD_2;
  5. public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd() {
  6. }
  7. public Object newInstance(String var1, String var2, boolean var3) {
  8. return new BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(var1, var2, var3);
  9. }
  10. public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(String var1, String var2, boolean var3) {
  11. this.FIELD_0 = var1;
  12. this.FIELD_1 = var2;
  13. this.FIELD_2 = var3;
  14. }
  15. //省去hashCode等方法。。。
  16. }

继续跟踪Generator.create方法,由于Generator是继承AbstractClassGenerator,这个AbstractClassGenerator是cglib用来生成字节码的一个模板类,Generator的super.create其实调用AbstractClassGenerator的create方法,最终会调用到Generator的模板方法generateClass方法,我们不去细究AbstractClassGenerator的细节,重点看generateClass。
这个是一个生成java类的方法,理解起来就好像我们平时写代码一样。

  1. public void generateClass(ClassVisitor v) {
  2. Type sourceType = Type.getType(source);
  3. Type targetType = Type.getType(target);
  4. ClassEmitter ce = new ClassEmitter(v);
  5. //开始“写”类,这里有修饰符、类名、父类等信息
  6. ce.begin_class(Constants.V1_2,
  7. Constants.ACC_PUBLIC,
  8. getClassName(),
  9. BEAN_COPIER,
  10. null,
  11. Constants.SOURCE_FILE);
  12. //没有构造方法
  13. EmitUtils.null_constructor(ce);
  14. //开始“写”一个方法,方法名是copy
  15. CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
  16. //通过Introspector获取source类和target类的PropertyDescriptor
  17. PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
  18. PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
  19. Map names = new HashMap();
  20. for (int i = 0; i < getters.length; i++) {
  21. names.put(getters[i].getName(), getters[i]);
  22. }
  23. Local targetLocal = e.make_local();
  24. Local sourceLocal = e.make_local();
  25. if (useConverter) {
  26. e.load_arg(1);
  27. e.checkcast(targetType);
  28. e.store_local(targetLocal);
  29. e.load_arg(0);
  30. e.checkcast(sourceType);
  31. e.store_local(sourceLocal);
  32. } else {
  33. e.load_arg(1);
  34. e.checkcast(targetType);
  35. e.load_arg(0);
  36. e.checkcast(sourceType);
  37. }
  38. //通过属性名来生成转换的代码
  39. //以setter作为遍历
  40. for (int i = 0; i < setters.length; i++) {
  41. PropertyDescriptor setter = setters[i];
  42. //根据setter的name获取getter
  43. PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
  44. if (getter != null) {
  45. //获取读写方法
  46. MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
  47. MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
  48. //如果用了useConverter,则进行下面的拼装代码方式
  49. if (useConverter) {
  50. Type setterType = write.getSignature().getArgumentTypes()[0];
  51. e.load_local(targetLocal);
  52. e.load_arg(2);
  53. e.load_local(sourceLocal);
  54. e.invoke(read);
  55. e.box(read.getSignature().getReturnType());
  56. EmitUtils.load_class(e, setterType);
  57. e.push(write.getSignature().getName());
  58. e.invoke_interface(CONVERTER, CONVERT);
  59. e.unbox_or_zero(setterType);
  60. e.invoke(write);
  61. //compatible用来判断getter和setter是否类型一致
  62. } else if (compatible(getter, setter)) {
  63. e.dup2();
  64. e.invoke(read);
  65. e.invoke(write);
  66. }
  67. }
  68. }
  69. e.return_value();
  70. e.end_method();
  71. ce.end_class();
  72. }
  73. private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
  74. // TODO: allow automatic widening conversions?
  75. return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
  76. }
  77. 即使没有使用过cglib也能读懂生成代码的流程吧,我们看看没有使用useConverter的情况下生成的代码:
  78. public class Object$$BeanCopierByCGLIB$$d1d970c8 extends BeanCopier {
  79. public Object$$BeanCopierByCGLIB$$d1d970c8() {
  80. }
  81. public void copy(Object var1, Object var2, Converter var3) {
  82. TargetVO var10000 = (TargetVO)var2;
  83. SourceVO var10001 = (SourceVO)var1;
  84. var10000.setDate1(((SourceVO)var1).getDate1());
  85. var10000.setIn(var10001.getIn());
  86. var10000.setListData(var10001.getListData());
  87. var10000.setMapData(var10001.getMapData());
  88. var10000.setP1(var10001.getP1());
  89. var10000.setP2(var10001.getP2());
  90. var10000.setP3(var10001.getP3());
  91. var10000.setPattr1(var10001.getPattr1());
  92. }
  93. }

在对比上面生成代码的代码是不是阔然开朗了。
再看看使用useConverter的情况:

  1. public class Object$$BeanCopierByCGLIB$$d1d970c7 extends BeanCopier {
  2. private static final Class CGLIB$load_class$java$2Eutil$2EDate;
  3. private static final Class CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner;
  4. private static final Class CGLIB$load_class$java$2Eutil$2EList;
  5. private static final Class CGLIB$load_class$java$2Eutil$2EMap;
  6. private static final Class CGLIB$load_class$java$2Elang$2EInteger;
  7. private static final Class CGLIB$load_class$java$2Elang$2ELong;
  8. private static final Class CGLIB$load_class$java$2Elang$2EByte;
  9. private static final Class CGLIB$load_class$java$2Elang$2EString;
  10. public Object$$BeanCopierByCGLIB$$d1d970c7() {
  11. }
  12. public void copy(Object var1, Object var2, Converter var3) {
  13. TargetVO var4 = (TargetVO)var2;
  14. SourceVO var5 = (SourceVO)var1;
  15. var4.setDate1((Date)var3.convert(var5.getDate1(), CGLIB$load_class$java$2Eutil$2EDate, "setDate1"));
  16. var4.setIn((Inner)var3.convert(var5.getIn(), CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner, "setIn"));
  17. var4.setListData((List)var3.convert(var5.getListData(), CGLIB$load_class$java$2Eutil$2EList, "setListData"));
  18. var4.setMapData((Map)var3.convert(var5.getMapData(), CGLIB$load_class$java$2Eutil$2EMap, "setMapData"));
  19. var4.setP1((Integer)var3.convert(var5.getP1(), CGLIB$load_class$java$2Elang$2EInteger, "setP1"));
  20. var4.setP2((Long)var3.convert(var5.getP2(), CGLIB$load_class$java$2Elang$2ELong, "setP2"));
  21. var4.setP3((Byte)var3.convert(var5.getP3(), CGLIB$load_class$java$2Elang$2EByte, "setP3"));
  22. var4.setPattr1((String)var3.convert(var5.getPattr1(), CGLIB$load_class$java$2Elang$2EString, "setPattr1"));
  23. var4.setSeq((Long)var3.convert(var5.getSeq(), CGLIB$load_class$java$2Elang$2ELong, "setSeq"));
  24. }
  25. static void CGLIB$STATICHOOK1() {
  26. CGLIB$load_class$java$2Eutil$2EDate = Class.forName("java.util.Date");
  27. CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner = Class.forName("beanmapper_compare.vo.SourceVO$Inner");
  28. CGLIB$load_class$java$2Eutil$2EList = Class.forName("java.util.List");
  29. CGLIB$load_class$java$2Eutil$2EMap = Class.forName("java.util.Map");
  30. CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
  31. CGLIB$load_class$java$2Elang$2ELong = Class.forName("java.lang.Long");
  32. CGLIB$load_class$java$2Elang$2EByte = Class.forName("java.lang.Byte");
  33. CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");
  34. }
  35. static {
  36. CGLIB$STATICHOOK1();
  37. }
  38. }

小结

BeanCopier性能确实很高,但从源码可以看出BeanCopier只会拷贝名称和类型都相同的属性,而且如果一旦使用Converter,BeanCopier只使用Converter定义的规则去拷贝属性,所以在convert方法中要考虑所有的属性。

Dozer

使用

上面提到的BeanUtils和BeanCopier都是功能比较简单的,需要属性名称一样,甚至类型也要一样。但是在大多数情况下这个要求就相对苛刻了,要知道有些VO由于各种原因不能修改,有些是外部接口SDK的对象,
有些对象的命名规则不同,例如有驼峰型的,有下划线的等等,各种什么情况都有。所以我们更加需要的是更加灵活丰富的功能,甚至可以做到定制化的转换。
Dozer就提供了这些功能,有支持同名隐式映射,支持基本类型互相转换,支持显示指定映射关系,支持exclude字段,支持递归匹配映射,支持深度匹配,支持Date to String的date-formate,支持自定义转换Converter,支持一次mapping定义多处使用,支持EventListener事件监听等等。不仅如此,Dozer在使用方式上,除了支持API,还支持XML和注解,满足大家的喜好。更多的功能可以参考这里
由于其功能很丰富,不可能每个都演示,这里只是给个大概认识,更详细的功能,或者XML和注解的配置,请看官方文档。

  1. private Mapper dozerMapper;
  2. @Before
  3. public void setup(){
  4. dozerMapper = DozerBeanMapperBuilder.create()
  5. .withMappingBuilder(new BeanMappingBuilder() {
  6. @Override
  7. protected void configure() {
  8. mapping(SourceVO.class, TargetVO.class)
  9. .fields("fullName", "name")
  10. .exclude("in");
  11. }
  12. })
  13. .withCustomConverter(null)
  14. .withEventListener(null)
  15. .build();
  16. }
  17. @Test
  18. public void dozerTest(){
  19. SourceVO sourceVO = getSourceVO();
  20. log.info("sourceVO={}", GsonUtil.toJson(sourceVO));
  21. TargetVO map = dozerMapper.map(sourceVO, TargetVO.class);
  22. log.info("map={}", GsonUtil.toJson(map));
  23. }

原理

Dozer的实现原理本质上还是用反射/Introspector那套,但是其丰富的功能,以及支持多种实现方式(API、XML、注解)使得代码看上去有点复杂,在翻阅代码时,我们大可不必理会这些类,只需要知道它们大体的作用就行了,重点关注核心流程和代码的实现。下面我们重点看看构建mapper的build方法和实现映射的map方法。
build方法很简单,它是一个初始化的动作,就是通过用户的配置来构建出一系列后面要用到的配置对象、上下文对象,或其他封装对象,我们不必深究这些对象是怎么实现的,从名字上我们大概能猜出这些对象是干嘛,负责什么就可以了。

  1. DozerBeanMapper(List<String> mappingFiles,
  2. BeanContainer beanContainer,
  3. DestBeanCreator destBeanCreator,
  4. DestBeanBuilderCreator destBeanBuilderCreator,
  5. BeanMappingGenerator beanMappingGenerator,
  6. PropertyDescriptorFactory propertyDescriptorFactory,
  7. List<CustomConverter> customConverters,
  8. List<MappingFileData> mappingsFileData,
  9. List<EventListener> eventListeners,
  10. CustomFieldMapper customFieldMapper,
  11. Map<String, CustomConverter> customConvertersWithId,
  12. ClassMappings customMappings,
  13. Configuration globalConfiguration,
  14. CacheManager cacheManager) {
  15. this.beanContainer = beanContainer;
  16. this.destBeanCreator = destBeanCreator;
  17. this.destBeanBuilderCreator = destBeanBuilderCreator;
  18. this.beanMappingGenerator = beanMappingGenerator;
  19. this.propertyDescriptorFactory = propertyDescriptorFactory;
  20. this.customConverters = new ArrayList<>(customConverters);
  21. this.eventListeners = new ArrayList<>(eventListeners);
  22. this.mappingFiles = new ArrayList<>(mappingFiles);
  23. this.customFieldMapper = customFieldMapper;
  24. this.customConvertersWithId = new HashMap<>(customConvertersWithId);
  25. this.eventManager = new DefaultEventManager(eventListeners);
  26. this.customMappings = customMappings;
  27. this.globalConfiguration = globalConfiguration;
  28. this.cacheManager = cacheManager;
  29. }

map方法是映射对象的过程,其入口是MappingProcessor的mapGeneral方法

  1. private <T> T mapGeneral(Object srcObj, final Class<T> destClass, final T destObj, final String mapId) {
  2. srcObj = MappingUtils.deProxy(srcObj, beanContainer);
  3. Class<T> destType;
  4. T result;
  5. if (destClass == null) {
  6. destType = (Class<T>)destObj.getClass();
  7. result = destObj;
  8. } else {
  9. destType = destClass;
  10. result = null;
  11. }
  12. ClassMap classMap = null;
  13. try {
  14. //构建ClassMap
  15. //ClassMap是包括src类和dest类和其他配置的一个封装
  16. classMap = getClassMap(srcObj.getClass(), destType, mapId);
  17. //注册事件
  18. eventManager.on(new DefaultEvent(EventTypes.MAPPING_STARTED, classMap, null, srcObj, result, null));
  19. //看看有没有自定义converter
  20. Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcObj
  21. .getClass(), destType);
  22. if (destObj == null) {
  23. // If this is a nested MapperAware conversion this mapping can be already processed
  24. // but we can do this optimization only in case of no destObject, instead we must copy to the dest object
  25. Object alreadyMappedValue = mappedFields.getMappedValue(srcObj, destType, mapId);
  26. if (alreadyMappedValue != null) {
  27. return (T)alreadyMappedValue;
  28. }
  29. }
  30. //优先使用自定义converter进行映射
  31. if (converterClass != null) {
  32. return (T)mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);
  33. }
  34. //也是对配置进行了封装
  35. BeanCreationDirective creationDirective =
  36. new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType,
  37. classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod(),
  38. classMap.getDestClass().isSkipConstructor());
  39. //继续进行映射
  40. result = createByCreationDirectiveAndMap(creationDirective, classMap, srcObj, result, false, null);
  41. } catch (Throwable e) {
  42. MappingUtils.throwMappingException(e);
  43. }
  44. eventManager.on(new DefaultEvent(EventTypes.MAPPING_FINISHED, classMap, null, srcObj, result, null));
  45. return result;
  46. }

一般情况下createByCreationDirectiveAndMap方法会一直调用到mapFromFieldMap方法,而在没有自定义converter的情况下会调用mapOrRecurseObject方法。
大多数情况下字段的映射会在这个方法做一般的解析

  1. private Object mapOrRecurseObject(Object srcObj, Object srcFieldValue, Class<?> destFieldType, FieldMap fieldMap, Object destObj) {
  2. Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMap.getSrcFieldType(srcObj.getClass());
  3. Class<?> converterClass = MappingUtils.determineCustomConverter(fieldMap, converterByDestTypeCache, fieldMap.getClassMap()
  4. .getCustomConverters(), srcFieldClass, destFieldType);
  5. //自定义converter的处理
  6. if (converterClass != null) {
  7. return mapUsingCustomConverter(converterClass, srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMap, false);
  8. }
  9. if (srcFieldValue == null) {
  10. return null;
  11. }
  12. String srcFieldName = fieldMap.getSrcFieldName();
  13. String destFieldName = fieldMap.getDestFieldName();
  14. if (!(DozerConstants.SELF_KEYWORD.equals(srcFieldName) && DozerConstants.SELF_KEYWORD.equals(destFieldName))) {
  15. Object alreadyMappedValue = mappedFields.getMappedValue(srcFieldValue, destFieldType, fieldMap.getMapId());
  16. if (alreadyMappedValue != null) {
  17. return alreadyMappedValue;
  18. }
  19. }
  20. //如果只是浅拷贝则直接返回(可配置)
  21. if (fieldMap.isCopyByReference()) {
  22. // just get the src and return it, no transformation.
  23. return srcFieldValue;
  24. }
  25. //对Map类型的处理
  26. boolean isSrcFieldClassSupportedMap = MappingUtils.isSupportedMap(srcFieldClass);
  27. boolean isDestFieldTypeSupportedMap = MappingUtils.isSupportedMap(destFieldType);
  28. if (isSrcFieldClassSupportedMap && isDestFieldTypeSupportedMap) {
  29. return mapMap(srcObj, (Map<?, ?>)srcFieldValue, fieldMap, destObj);
  30. }
  31. if (fieldMap instanceof MapFieldMap && destFieldType.equals(Object.class)) {
  32. destFieldType = fieldMap.getDestHintContainer() != null ? fieldMap.getDestHintContainer().getHint() : srcFieldClass;
  33. }
  34. //对基本类型的映射处理
  35. //PrimitiveOrWrapperConverter类支持兼容了基本类型之间的互相转换
  36. if (primitiveConverter.accepts(srcFieldClass) || primitiveConverter.accepts(destFieldType)) {
  37. // Primitive or Wrapper conversion
  38. if (fieldMap.getDestHintContainer() != null) {
  39. Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
  40. // if the destType is null this means that there was more than one hint.
  41. // we must have already set the destType then.
  42. if (destHintType != null) {
  43. destFieldType = destHintType;
  44. }
  45. }
  46. //#1841448 - if trim-strings=true, then use a trimmed src string value when converting to dest value
  47. Object convertSrcFieldValue = srcFieldValue;
  48. if (fieldMap.isTrimStrings() && srcFieldValue.getClass().equals(String.class)) {
  49. convertSrcFieldValue = ((String)srcFieldValue).trim();
  50. }
  51. DateFormatContainer dfContainer = new DateFormatContainer(fieldMap.getDateFormat());
  52. if (fieldMap instanceof MapFieldMap && !primitiveConverter.accepts(destFieldType)) {
  53. return primitiveConverter.convert(convertSrcFieldValue, convertSrcFieldValue.getClass(), dfContainer);
  54. } else {
  55. return primitiveConverter.convert(convertSrcFieldValue, destFieldType, dfContainer, destFieldName, destObj);
  56. }
  57. }
  58. //对集合类型的映射处理
  59. if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {
  60. return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);
  61. }
  62. //对枚举类型的映射处理
  63. if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {
  64. return mapEnum((Enum)srcFieldValue, (Class<Enum>)destFieldType);
  65. }
  66. if (fieldMap.getDestDeepIndexHintContainer() != null) {
  67. destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();
  68. }
  69. //其他复杂对象类型的处理
  70. return mapCustomObject(fieldMap, destObj, destFieldType, destFieldName, srcFieldValue);
  71. }
  72. mapCustomObject方法。其实你会发现这个方法最重要的一点就是做递归处理,无论是最后调用createByCreationDirectiveAndMap还是mapToDestObject方法。
  73. private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, String destFieldName, Object srcFieldValue) {
  74. srcFieldValue = MappingUtils.deProxy(srcFieldValue, beanContainer);
  75. // Custom java bean. Need to make sure that the destination object is not
  76. // already instantiated.
  77. Object result = null;
  78. // in case of iterate feature new objects are created in any case
  79. if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {
  80. result = getExistingValue(fieldMap, destObj, destFieldType);
  81. }
  82. // if the field is not null than we don't want a new instance
  83. if (result == null) {
  84. // first check to see if this plain old field map has hints to the actual
  85. // type.
  86. if (fieldMap.getDestHintContainer() != null) {
  87. Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());
  88. // if the destType is null this means that there was more than one hint.
  89. // we must have already set the destType then.
  90. if (destHintType != null) {
  91. destFieldType = destHintType;
  92. }
  93. }
  94. // Check to see if explicit map-id has been specified for the field
  95. // mapping
  96. String mapId = fieldMap.getMapId();
  97. Class<?> targetClass;
  98. if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {
  99. targetClass = fieldMap.getDestHintContainer().getHint();
  100. } else {
  101. targetClass = destFieldType;
  102. }
  103. ClassMap classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);
  104. BeanCreationDirective creationDirective = new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),
  105. destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),
  106. fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() :
  107. classMap.getDestClassCreateMethod(),
  108. classMap.getDestClass().isSkipConstructor(), destObj, destFieldName);
  109. result = createByCreationDirectiveAndMap(creationDirective, classMap, srcFieldValue, null, false, fieldMap.getMapId());
  110. } else {
  111. mapToDestObject(null, srcFieldValue, result, false, fieldMap.getMapId());
  112. }
  113. return result;
  114. }

小结

Dozer功能强大,但底层还是用反射那套,所以在性能测试中它的表现一般,仅次于Apache的BeanUtils。如果不追求性能的话,可以使用。

Orika

Orika可以说是几乎集成了上述几个工具的优点,不仅具有丰富的功能,底层使用Javassist生成字节码,运行 效率很高的。

使用

Orika基本支持了Dozer支持的功能,这里我也是简单介绍一下Orika的使用,具体更详细的API可以参考User Guide。

  1. private MapperFactory mapperFactory;
  2. @Before
  3. public void setup() {
  4. mapperFactory = new DefaultMapperFactory.Builder().build();
  5. ConverterFactory converterFactory = mapperFactory.getConverterFactory();
  6. converterFactory.registerConverter(new TypeConverter());
  7. mapperFactory.classMap(SourceVO.class, TargetVO.class)
  8. .field("fullName", "name")
  9. .field("type", "enumType")
  10. .exclude("in")
  11. .byDefault()
  12. .register();
  13. }
  14. @Test
  15. public void main() {
  16. MapperFacade mapper = mapperFactory.getMapperFacade();
  17. SourceVO sourceVO = getSourceVO();
  18. log.info("sourceVO={}", GsonUtil.toJson(sourceVO));
  19. TargetVO map = mapper.map(sourceVO, TargetVO.class);
  20. log.info("map={}", GsonUtil.toJson(map));
  21. }

原理

在讲解实现原理时,我们先看看Orika在背后干了什么事情。
通过增加以下配置,我们可以看到Orika在做映射过程中生成mapper的源码和字节码。

  1. System.setProperty("ma.glasnost.orika.writeSourceFiles", "true");
  2. System.setProperty("ma.glasnost.orika.writeClassFiles", "true");
  3. System.setProperty("ma.glasnost.orika.writeSourceFilesToPath", "path");
  4. System.setProperty("ma.glasnost.orika.writeClassFilesToPath", "path");

用上面的例子,我们看看Orika生成的java代码:

  1. package ma.glasnost.orika.generated;
  2. public class Orika_TargetVO_SourceVO_Mapper947163525829122$0 extends ma.glasnost.orika.impl.GeneratedMapperBase {
  3. public void mapAtoB(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {
  4. super.mapAtoB(a, b, mappingContext);
  5. // sourceType: SourceVO
  6. beanmapper_compare.vo.SourceVO source = ((beanmapper_compare.vo.SourceVO)a);
  7. // destinationType: TargetVO
  8. beanmapper_compare.vo.TargetVO destination = ((beanmapper_compare.vo.TargetVO)b);
  9. destination.setName(((java.lang.String)source.getFullName()));
  10. if ( !(((java.lang.Integer)source.getType()) == null)){
  11. destination.setEnumType(((beanmapper_compare.vo.TargetVO.EnumType)((ma.glasnost.orika.Converter)usedConverters[0]).convert(((java.lang.Integer)source.getType()), ((ma.glasnost.orika.metadata.Type)usedTypes[0]), mappingContext)));
  12. } else {
  13. destination.setEnumType(null);
  14. }
  15. if ( !(((java.util.Date)source.getDate1()) == null)){
  16. destination.setDate1(((java.util.Date)((ma.glasnost.orika.Converter)usedConverters[1]).convert(((java.util.Date)source.getDate1()), ((ma.glasnost.orika.metadata.Type)usedTypes[1]), mappingContext)));
  17. } else {
  18. destination.setDate1(null);
  19. }if ( !(((java.util.List)source.getListData()) == null)) {
  20. java.util.List new_listData = ((java.util.List)new java.util.ArrayList());
  21. new_listData.addAll(mapperFacade.mapAsList(((java.util.List)source.getListData()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext));
  22. destination.setListData(new_listData);
  23. } else {
  24. if ( !(((java.util.List)destination.getListData()) == null)) {
  25. destination.setListData(null);
  26. };
  27. }if ( !(((java.util.Map)source.getMapData()) == null)){
  28. java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());
  29. for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {
  30. java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());
  31. java.lang.Integer newMapDataKey = null;
  32. java.util.List newMapDataVal = null;
  33. if ( !(((java.lang.Long)sourceMapDataEntry.getKey()) == null)){
  34. newMapDataKey = ((java.lang.Integer)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Long)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext));
  35. } else {
  36. newMapDataKey = null;
  37. }
  38. if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {
  39. java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());
  40. new_newMapDataVal.addAll(mapperFacade.mapAsList(((java.util.List)sourceMapDataEntry.getValue()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), ((ma.glasnost.orika.metadata.Type)usedTypes[4]), mappingContext));
  41. newMapDataVal = new_newMapDataVal;
  42. } else {
  43. if ( !(newMapDataVal == null)) {
  44. newMapDataVal = null;
  45. };
  46. }
  47. new_mapData.put(newMapDataKey, newMapDataVal);
  48. }
  49. destination.setMapData(new_mapData);
  50. } else {
  51. destination.setMapData(null);
  52. }
  53. destination.setP1(((java.lang.Integer)source.getP1()));
  54. destination.setP2(((java.lang.Long)source.getP2()));
  55. destination.setP3(((java.lang.Byte)source.getP3()));
  56. destination.setPattr1(((java.lang.String)source.getPattr1()));
  57. if ( !(((java.lang.String)source.getSeq()) == null)){
  58. destination.setSeq(((java.lang.Long)((ma.glasnost.orika.Converter)usedConverters[3]).convert(((java.lang.String)source.getSeq()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext)));
  59. } else {
  60. destination.setSeq(null);
  61. }
  62. if(customMapper != null) {
  63. customMapper.mapAtoB(source, destination, mappingContext);
  64. }
  65. }
  66. public void mapBtoA(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {
  67. super.mapBtoA(a, b, mappingContext);
  68. // sourceType: TargetVO
  69. beanmapper_compare.vo.TargetVO source = ((beanmapper_compare.vo.TargetVO)a);
  70. // destinationType: SourceVO
  71. beanmapper_compare.vo.SourceVO destination = ((beanmapper_compare.vo.SourceVO)b);
  72. destination.setFullName(((java.lang.String)source.getName()));
  73. if ( !(((beanmapper_compare.vo.TargetVO.EnumType)source.getEnumType()) == null)){
  74. destination.setType(((java.lang.Integer)((ma.glasnost.orika.Converter)usedConverters[0]).convert(((beanmapper_compare.vo.TargetVO.EnumType)source.getEnumType()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext)));
  75. } else {
  76. destination.setType(null);
  77. }
  78. if ( !(((java.util.Date)source.getDate1()) == null)){
  79. destination.setDate1(((java.util.Date)((ma.glasnost.orika.Converter)usedConverters[1]).convert(((java.util.Date)source.getDate1()), ((ma.glasnost.orika.metadata.Type)usedTypes[1]), mappingContext)));
  80. } else {
  81. destination.setDate1(null);
  82. }if ( !(((java.util.List)source.getListData()) == null)) {
  83. java.util.List new_listData = ((java.util.List)new java.util.ArrayList());
  84. new_listData.addAll(mapperFacade.mapAsList(((java.util.List)source.getListData()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
  85. destination.setListData(new_listData);
  86. } else {
  87. if ( !(((java.util.List)destination.getListData()) == null)) {
  88. destination.setListData(null);
  89. };
  90. }if ( !(((java.util.Map)source.getMapData()) == null)){
  91. java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());
  92. for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {
  93. java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());
  94. java.lang.Long newMapDataKey = null;
  95. java.util.List newMapDataVal = null;
  96. if ( !(((java.lang.Integer)sourceMapDataEntry.getKey()) == null)){
  97. newMapDataKey = ((java.lang.Long)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Integer)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
  98. } else {
  99. newMapDataKey = null;
  100. }
  101. if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {
  102. java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());
  103. new_newMapDataVal.addAll(mapperFacade.mapAsList(((java.util.List)sourceMapDataEntry.getValue()), ((ma.glasnost.orika.metadata.Type)usedTypes[4]), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));
  104. newMapDataVal = new_newMapDataVal;
  105. } else {
  106. if ( !(newMapDataVal == null)) {
  107. newMapDataVal = null;
  108. };
  109. }
  110. new_mapData.put(newMapDataKey, newMapDataVal);
  111. }
  112. destination.setMapData(new_mapData);
  113. } else {
  114. destination.setMapData(null);
  115. }
  116. destination.setP1(((java.lang.Integer)source.getP1()));
  117. destination.setP2(((java.lang.Long)source.getP2()));
  118. destination.setP3(((java.lang.Byte)source.getP3()));
  119. destination.setPattr1(((java.lang.String)source.getPattr1()));
  120. if ( !(((java.lang.Long)source.getSeq()) == null)){
  121. destination.setSeq(((java.lang.String)((ma.glasnost.orika.Converter)usedConverters[4]).convert(((java.lang.Long)source.getSeq()), ((ma.glasnost.orika.metadata.Type)usedTypes[5]), mappingContext)));
  122. } else {
  123. destination.setSeq(null);
  124. }
  125. if(customMapper != null) {
  126. customMapper.mapBtoA(source, destination, mappingContext);
  127. }
  128. }
  129. }

这个mapper类就两个方法mapAtoB和mapBtoA,从名字看猜到前者是负责src -> dest的映射,后者是负责dest -> src的映射。
好,我们们看看实现的过程。
Orika的使用跟Dozer的类似,首先通过配置生成一个MapperFactory,再用MapperFacade来作为映射的统一入口,这里MapperFactory和MapperFacade都是单例的。mapperFactory在做配置类映射时,只是注册了ClassMap,还没有真正的生成mapper的字节码,是在第一次调用getMapperFacade方法时才初始化mapper。下面看看getMapperFacade。
(源码基于 ma.glasnost.orika:orika-core:1.5.4)

  1. public MapperFacade getMapperFacade() {
  2. if (!isBuilt) {
  3. synchronized (mapperFacade) {
  4. if (!isBuilt) {
  5. build();
  6. }
  7. }
  8. }
  9. return mapperFacade;
  10. }
  11. 利用注册的ClassMap信息和MappingContext上下文信息来构造mapper
  12. public synchronized void build() {
  13. if (!isBuilding && !isBuilt) {
  14. isBuilding = true;
  15. MappingContext context = contextFactory.getContext();
  16. try {
  17. if (useBuiltinConverters) {
  18. BuiltinConverters.register(converterFactory);
  19. }
  20. converterFactory.setMapperFacade(mapperFacade);
  21. for (Map.Entry<MapperKey, ClassMap<Object, Object>> classMapEntry : classMapRegistry.entrySet()) {
  22. ClassMap<Object, Object> classMap = classMapEntry.getValue();
  23. if (classMap.getUsedMappers().isEmpty()) {
  24. classMapEntry.setValue(classMap.copyWithUsedMappers(discoverUsedMappers(classMap)));
  25. }
  26. }
  27. buildClassMapRegistry();
  28. Map<ClassMap<?, ?>, GeneratedMapperBase> generatedMappers = new HashMap<ClassMap<?, ?>, GeneratedMapperBase>();
  29. //重点看这里
  30. //在使用mapperFactory配置classMap时,会存放在classMapRegistry里
  31. for (ClassMap<?, ?> classMap : classMapRegistry.values()) {
  32. //对每个classMap生成一个mapper,重点看buildMapper方法
  33. generatedMappers.put(classMap, buildMapper(classMap, false, context));
  34. }
  35. Set<Entry<ClassMap<?, ?>, GeneratedMapperBase>> generatedMapperEntries = generatedMappers.entrySet();
  36. for (Entry<ClassMap<?, ?>, GeneratedMapperBase> generatedMapperEntry : generatedMapperEntries) {
  37. buildObjectFactories(generatedMapperEntry.getKey(), context);
  38. initializeUsedMappers(generatedMapperEntry.getValue(), generatedMapperEntry.getKey(), context);
  39. }
  40. } finally {
  41. contextFactory.release(context);
  42. }
  43. isBuilt = true;
  44. isBuilding = false;
  45. }
  46. }
  47. public Set<ClassMap<Object, Object>> lookupUsedClassMap(MapperKey mapperKey) {
  48. Set<ClassMap<Object, Object>> usedClassMapSet = usedMapperMetadataRegistry.get(mapperKey);
  49. if (usedClassMapSet == null) {
  50. usedClassMapSet = Collections.emptySet();
  51. }
  52. return usedClassMapSet;
  53. }
  54. 跟踪buildMapper方法
  55. private GeneratedMapperBase buildMapper(ClassMap<?, ?> classMap, boolean isAutoGenerated, MappingContext context) {
  56. register(classMap.getAType(), classMap.getBType(), isAutoGenerated);
  57. register(classMap.getBType(), classMap.getAType(), isAutoGenerated);
  58. final MapperKey mapperKey = new MapperKey(classMap.getAType(), classMap.getBType());
  59. //调用mapperGenerator的build方法生成mapper
  60. final GeneratedMapperBase mapper = mapperGenerator.build(classMap, context);
  61. mapper.setMapperFacade(mapperFacade);
  62. mapper.setFromAutoMapping(isAutoGenerated);
  63. if (classMap.getCustomizedMapper() != null) {
  64. final Mapper<Object, Object> customizedMapper = (Mapper<Object, Object>) classMap.getCustomizedMapper();
  65. mapper.setCustomMapper(customizedMapper);
  66. }
  67. mappersRegistry.remove(mapper);
  68. //生成的mapper存放到mappersRegistry
  69. mappersRegistry.add(mapper);
  70. classMapRegistry.put(mapperKey, (ClassMap<Object, Object>) classMap);
  71. return mapper;
  72. }
  73. MapperGeneratorbuild方法
  74. public GeneratedMapperBase build(ClassMap<?, ?> classMap, MappingContext context) {
  75. StringBuilder logDetails = null;
  76. try {
  77. compilerStrategy.assureTypeIsAccessible(classMap.getAType().getRawType());
  78. compilerStrategy.assureTypeIsAccessible(classMap.getBType().getRawType());
  79. if (LOGGER.isDebugEnabled()) {
  80. logDetails = new StringBuilder();
  81. String srcName = TypeFactory.nameOf(classMap.getAType(), classMap.getBType());
  82. String dstName = TypeFactory.nameOf(classMap.getBType(), classMap.getAType());
  83. logDetails.append("Generating new mapper for (" + srcName + ", " + dstName + ")");
  84. }
  85. //构建用来生成源码及字节码的上下文
  86. final SourceCodeContext mapperCode = new SourceCodeContext(classMap.getMapperClassName(), GeneratedMapperBase.class, context,
  87. logDetails);
  88. Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>();
  89. //增加mapAtoB方法
  90. mappedFields.addAll(addMapMethod(mapperCode, true, classMap, logDetails));
  91. //增加mapBtoA方法
  92. //addMapMethod方法基本就是手写代码的过程,有兴趣的读者可以看看
  93. mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails));
  94. //生成一个mapper实例
  95. GeneratedMapperBase instance = mapperCode.getInstance();
  96. instance.setAType(classMap.getAType());
  97. instance.setBType(classMap.getBType());
  98. instance.setFavorsExtension(classMap.favorsExtension());
  99. if (logDetails != null) {
  100. LOGGER.debug(logDetails.toString());
  101. logDetails = null;
  102. }
  103. classMap = classMap.copy(mappedFields);
  104. context.registerMapperGeneration(classMap);
  105. return instance;
  106. } catch (final Exception e) {
  107. if (logDetails != null) {
  108. logDetails.append("\n<---- ERROR occurred here");
  109. LOGGER.debug(logDetails.toString());
  110. }
  111. throw new MappingException(e);
  112. }
  113. 生成mapper实例
  114. T instance = (T) compileClass().newInstance();
  115. protected Class<?> compileClass() throws SourceCodeGenerationException {
  116. try {
  117. return compilerStrategy.compileClass(this);
  118. } catch (SourceCodeGenerationException e) {
  119. throw e;
  120. }
  121. }
  122. 这里的compilerStrategy的默认是用Javassist(你也可以自定义生成字节码的策略)
  123. JavassistCompilerStrategycompileClass方法
  124. 这基本上就是一个使用Javassist的过程,经过前面的各种铺垫(通过配置信息、上下文信息、拼装java源代码等等),终于来到这一步
  125. public Class<?> compileClass(SourceCodeContext sourceCode) throws SourceCodeGenerationException {
  126. StringBuilder className = new StringBuilder(sourceCode.getClassName());
  127. CtClass byteCodeClass = null;
  128. int attempts = 0;
  129. Random rand = RANDOM;
  130. while (byteCodeClass == null) {
  131. try {
  132. //创建一个类
  133. byteCodeClass = classPool.makeClass(className.toString());
  134. } catch (RuntimeException e) {
  135. if (attempts < 5) {
  136. className.append(Integer.toHexString(rand.nextInt()));
  137. } else {
  138. // No longer likely to be accidental name collision;
  139. // propagate the error
  140. throw e;
  141. }
  142. }
  143. }
  144. CtClass abstractMapperClass;
  145. Class<?> compiledClass;
  146. try {
  147. //把源码写到磁盘(通过上面提到的配置)
  148. writeSourceFile(sourceCode);
  149. Boolean existing = superClasses.put(sourceCode.getSuperClass(), true);
  150. if (existing == null || !existing) {
  151. classPool.insertClassPath(new ClassClassPath(sourceCode.getSuperClass()));
  152. }
  153. if (registerClassLoader(Thread.currentThread().getContextClassLoader())) {
  154. classPool.insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
  155. }
  156. abstractMapperClass = classPool.get(sourceCode.getSuperClass().getCanonicalName());
  157. byteCodeClass.setSuperclass(abstractMapperClass);
  158. //增加字段
  159. for (String fieldDef : sourceCode.getFields()) {
  160. try {
  161. byteCodeClass.addField(CtField.make(fieldDef, byteCodeClass));
  162. } catch (CannotCompileException e) {
  163. LOG.error("An exception occurred while compiling: " + fieldDef + " for " + sourceCode.getClassName(), e);
  164. throw e;
  165. }
  166. }
  167. //增加方法,这里主要就是mapAtoB和mapBtoA方法
  168. //直接用源码通过Javassist往类“加”方法
  169. for (String methodDef : sourceCode.getMethods()) {
  170. try {
  171. byteCodeClass.addMethod(CtNewMethod.make(methodDef, byteCodeClass));
  172. } catch (CannotCompileException e) {
  173. LOG.error(
  174. "An exception occured while compiling the following method:\n\n " + methodDef + "\n\n for "
  175. + sourceCode.getClassName() + "\n", e);
  176. throw e;
  177. }
  178. }
  179. //生成类
  180. compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());
  181. //字节码文件写磁盘
  182. writeClassFile(sourceCode, byteCodeClass);
  183. } catch (NotFoundException e) {
  184. throw new SourceCodeGenerationException(e);
  185. } catch (CannotCompileException e) {
  186. throw new SourceCodeGenerationException("Error compiling " + sourceCode.getClassName(), e);
  187. } catch (IOException e) {
  188. throw new SourceCodeGenerationException("Could not write files for " + sourceCode.getClassName(), e);
  189. }
  190. return compiledClass;
  191. }

好,mapper类生成了,现在就看在调用MapperFacade的map方法是如何使用这个mapper类的。
其实很简单,还记得生成的mapper是放到mappersRegistry吗,跟踪代码,在resolveMappingStrategy方法根据typeA和typeB在mappersRegistry找到mapper,在调用mapper的mapAtoB或mapBtoA方法即可。

小结

总体来说,Orika是一个功能强大的而且性能很高的工具,推荐使用。

总结

通过对BeanUtils、BeanCopier、Dozer、Orika这几个工具的对比,我们得知了它们的性能以及实现原理。在使用时,我们可以根据自己的实际情况选择,推荐使用Orika。另外,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java 系列面试题和答案,非常齐全。

参考:https://mp.weixin.qq.com/s/cV0jOipl5wg_PLMXtE7Ygg