BeanUtils、BeanCopier、Dozer、Orika 哪个性能最强?

开发 后端
本文将讲述上面几个工具的使用、性能对比及原理分析。

 背景

在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有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下运行后的结果如下:

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的使用是一样的:

  1. 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. }   

即使没有使用过cglib也能读懂生成代码的流程吧,我们看看没有使用useConverter的情况下生成的代码: 

  1. public class Object$$BeanCopierByCGLIB$$d1d970c8 extends BeanCopier {    
  2.     public Object$$BeanCopierByCGLIB$$d1d970c8() {    
  3.     }      
  4.     public void copy(Object var1, Object var2, Converter var3) {    
  5.         TargetVO var10000 = (TargetVO)var2;    
  6.         SourceVO var10001 = (SourceVO)var1;    
  7.         var10000.setDate1(((SourceVO)var1).getDate1());    
  8.         var10000.setIn(var10001.getIn());   
  9.         var10000.setListData(var10001.getListData());   
  10.         var10000.setMapData(var10001.getMapData());    
  11.         var10000.setP1(var10001.getP1());    
  12.         var10000.setP2(var10001.getP2());    
  13.         var10000.setP3(var10001.getP3());    
  14.         var10000.setPattr1(var10001.getPattr1());    
  15.     }    
  16. }   

在对比上面生成代码的代码是不是阔然开朗了。

再看看使用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方法。

2021 最新 Java 面试题出炉!(带全部答案)

大多数情况下字段的映射会在这个方法做一般的解析 

  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. }   

mapCustomObject方法。其实你会发现这个方法最重要的一点就是做递归处理,无论是最后调用createByCreationDirectiveAndMap还是mapToDestObject方法。 

  1. private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, String destFieldName, Object srcFieldValue) {    
  2.     srcFieldValue = MappingUtils.deProxy(srcFieldValue, beanContainer); 
  3.      // Custom java bean. Need to make sure that the destination object is not   
  4.     // already instantiated.    
  5.     Object result = null;    
  6.     // in case of iterate feature new objects are created in any case    
  7.     if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {    
  8.         result = getExistingValue(fieldMap, destObj, destFieldType);    
  9.     }    
  10.     // if the field is not null than we don't want a new instance    
  11.     if (result == null) {    
  12.         // first check to see if this plain old field map has hints to the actual    
  13.         // type.    
  14.         if (fieldMap.getDestHintContainer() != null) {    
  15.             Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());    
  16.             // if the destType is null this means that there was more than one hint.    
  17.             // we must have already set the destType then.    
  18.             if (destHintType != null) {    
  19.                 destFieldType = destHintType;   
  20.             }    
  21.         }    
  22.         // Check to see if explicit map-id has been specified for the field    
  23.         // mapping    
  24.         String mapId = fieldMap.getMapId();    
  25.         Class<?> targetClass;    
  26.         if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {    
  27.             targetClass = fieldMap.getDestHintContainer().getHint();    
  28.         } else {    
  29.             targetClass = destFieldType;    
  30.         }    
  31.         ClassMap classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);    
  32.         BeanCreationDirective creationDirective = new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),  
  33.                                                                             destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),   
  34.                                                                              fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() :   
  35.                                                                                      classMap.getDestClassCreateMethod(),    
  36.                                                                             classMap.getDestClass().isSkipConstructor(), destObj, destFieldName);    
  37.         result = createByCreationDirectiveAndMap(creationDirective, classMap, srcFieldValue, null, false, fieldMap.getMapId());    
  38.     } else {    
  39.         mapToDestObject(null, srcFieldValue, result, false, fieldMap.getMapId());    
  40.     }    
  41.     return result;    
  42. }   

小结

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. }   

利用注册的ClassMap信息和MappingContext上下文信息来构造mapper 

  1. public synchronized void build() {      
  2.      if (!isBuilding && !isBuilt) {    
  3.         isBuilding = true;           
  4.          MappingContext context = contextFactory.getContext();    
  5.         try {    
  6.             if (useBuiltinConverters) {    
  7.                 BuiltinConverters.register(converterFactory);  
  8.              }    
  9.             converterFactory.setMapperFacade(mapperFacade);              
  10.              for (Map.Entry<MapperKey, ClassMap<Object, Object>> classMapEntry : classMapRegistry.entrySet()) {   
  11.                  ClassMap<Object, Object> classMap = classMapEntry.getValue();   
  12.                  if (classMap.getUsedMappers().isEmpty()) {   
  13.                      classMapEntry.setValue(classMap.copyWithUsedMappers(discoverUsedMappers(classMap)));   
  14.                  }   
  15.              }    
  16.             buildClassMapRegistry();               
  17.              Map<ClassMap<??>, GeneratedMapperBase> generatedMappers = new HashMap<ClassMap<??>, GeneratedMapperBase>();   
  18.              //重点看这里   
  19.              //在使用mapperFactory配置classMap时,会存放在classMapRegistry里   
  20.              for (ClassMap<??> classMap : classMapRegistry.values()) {   
  21.                  //对每个classMap生成一个mapper,重点看buildMapper方法   
  22.                  generatedMappers.put(classMap, buildMapper(classMap, false, context));   
  23.              }              
  24.              Set<Entry<ClassMap<??>, GeneratedMapperBase>> generatedMapperEntries = generatedMappers.entrySet();   
  25.              for (Entry<ClassMap<??>, GeneratedMapperBase> generatedMapperEntry : generatedMapperEntries) {   
  26.                  buildObjectFactories(generatedMapperEntry.getKey(), context);   
  27.                  initializeUsedMappers(generatedMapperEntry.getValue(), generatedMapperEntry.getKey(), context);   
  28.              }              
  29.         } finally {    
  30.             contextFactory.release(context);    
  31.         }        
  32.          isBuilt = true;    
  33.         isBuilding = false;    
  34.     }    
  35. }    
  36. public Set<ClassMap<Object, Object>> lookupUsedClassMap(MapperKey mapperKey) {    
  37.     Set<ClassMap<Object, Object>> usedClassMapSet = usedMapperMetadataRegistry.get(mapperKey);    
  38.     if (usedClassMapSet == null) {    
  39.         usedClassMapSet = Collections.emptySet();    
  40.     }    
  41.     return usedClassMapSet;    
  42. }   

跟踪buildMapper方法 

  1. private GeneratedMapperBase buildMapper(ClassMap<??> classMap, boolean isAutoGenerated, MappingContext context) {      
  2.      register(classMap.getAType(), classMap.getBType(), isAutoGenerated);    
  3.     register(classMap.getBType(), classMap.getAType(), isAutoGenerated);       
  4.      final MapperKey mapperKey = new MapperKey(classMap.getAType(), classMap.getBType()); 
  5.      //调用mapperGenerator的build方法生成mapper    
  6.     final GeneratedMapperBase mapper = mapperGenerator.build(classMap, context);    
  7.     mapper.setMapperFacade(mapperFacade);    
  8.     mapper.setFromAutoMapping(isAutoGenerated);   
  9.      if (classMap.getCustomizedMapper() != null) {    
  10.         final Mapper<Object, Object> customizedMapper = (Mapper<Object, Object>) classMap.getCustomizedMapper();    
  11.         mapper.setCustomMapper(customizedMapper);    
  12.     }    
  13.     mappersRegistry.remove(mapper);    
  14.     //生成的mapper存放到mappersRegistry    
  15.     mappersRegistry.add(mapper);    
  16.     classMapRegistry.put(mapperKey, (ClassMap<Object, Object>) classMap);      
  17.      return mapper;   
  18. }   

MapperGenerator的build方法 

  1. public GeneratedMapperBase build(ClassMap<??> classMap, MappingContext context) {      
  2. StringBuilder logDetails = null;    
  3. try {    
  4.     compilerStrategy.assureTypeIsAccessible(classMap.getAType().getRawType());    
  5.     compilerStrategy.assureTypeIsAccessible(classMap.getBType().getRawType());        
  6.      if (LOGGER.isDebugEnabled()) {    
  7.         logDetails = new StringBuilder();    
  8.         String srcName = TypeFactory.nameOf(classMap.getAType(), classMap.getBType());    
  9.         String dstName = TypeFactory.nameOf(classMap.getBType(), classMap.getAType());    
  10.         logDetails.append("Generating new mapper for (" + srcName + ", " + dstName + ")");   
  11.     }      
  12.      //构建用来生成源码及字节码的上下文    
  13.     final SourceCodeContext mapperCode = new SourceCodeContext(classMap.getMapperClassName(), GeneratedMapperBase.class, context,   
  14.              logDetails);      
  15.      Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>();    
  16.     //增加mapAtoB方法    
  17.     mappedFields.addAll(addMapMethod(mapperCode, true, classMap, logDetails));    
  18.     //增加mapBtoA方法    
  19.     //addMapMethod方法基本就是手写代码的过程,有兴趣的读者可以看看    
  20.     mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails));        
  21.      //生成一个mapper实例    
  22.     GeneratedMapperBase instance = mapperCode.getInstance();    
  23.     instance.setAType(classMap.getAType());    
  24.     instance.setBType(classMap.getBType());    
  25.     instance.setFavorsExtension(classMap.favorsExtension());        
  26.      if (logDetails != null) {    
  27.         LOGGER.debug(logDetails.toString());    
  28.         logDetails = null;    
  29.     }       
  30.      classMapclassMap = classMap.copy(mappedFields);    
  31.     context.registerMapperGeneration(classMap);        
  32.      return instance;      
  33.  } catch (final Exception e) {    
  34.     if (logDetails != null) {    
  35.         logDetails.append("\n<---- ERROR occurred here");    
  36.         LOGGER.debug(logDetails.toString());    
  37.     }    
  38.     throw new MappingException(e);    
  39. }   

生成mapper实例 

  1. instance = (T) compileClass().newInstance();      
  2. protected Class<?> compileClass() throws SourceCodeGenerationException {    
  3.     try {    
  4.         return compilerStrategy.compileClass(this);    
  5.     } catch (SourceCodeGenerationException e) {    
  6.         throw e;    
  7.     }    
  8. }   

这里的compilerStrategy的默认是用Javassist(你也可以自定义生成字节码的策略)

JavassistCompilerStrategy的compileClass方法

这基本上就是一个使用Javassist的过程,经过前面的各种铺垫(通过配置信息、上下文信息、拼装java源代码等等),终于来到这一步 

  1. public Class<?> compileClass(SourceCodeContext sourceCode) throws SourceCodeGenerationException {    
  2.          StringBuilder className = new StringBuilder(sourceCode.getClassName());   
  3.     CtClass byteCodeClass = null;    
  4.     int attempts = 0;    
  5.     Random rand = RANDOM;    
  6.     while (byteCodeClass == null) {    
  7.         try {    
  8.             //创建一个类    
  9.             byteCodeClass = classPool.makeClass(className.toString());   
  10.         } catch (RuntimeException e) {    
  11.             if (attempts < 5) {    
  12.                 className.append(Integer.toHexString(rand.nextInt()));    
  13.             } else {    
  14.                 // No longer likely to be accidental name collision;    
  15.                 // propagate the error    
  16.                 throw e;    
  17.             }    
  18.         }    
  19.     }    
  20.      CtClass abstractMapperClass;    
  21.     Class<?> compiledClass;        
  22.      try {    
  23.         //把源码写到磁盘(通过上面提到的配置)    
  24.         writeSourceFile(sourceCode);          
  25.          Boolean existing = superClasses.put(sourceCode.getSuperClass(), true);    
  26.         if (existing == null || !existing) {   
  27.              classPool.insertClassPath(new ClassClassPath(sourceCode.getSuperClass()));    
  28.         }           
  29.          if (registerClassLoader(Thread.currentThread().getContextClassLoader())) {    
  30.             classPool.insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));    
  31.         }           
  32.          abstractMapperClass = classPool.get(sourceCode.getSuperClass().getCanonicalName());    
  33.         byteCodeClass.setSuperclass(abstractMapperClass);            
  34.          //增加字段    
  35.         for (String fieldDef : sourceCode.getFields()) {    
  36.             try {    
  37.                 byteCodeClass.addField(CtField.make(fieldDef, byteCodeClass));    
  38.             } catch (CannotCompileException e) {    
  39.                 LOG.error("An exception occurred while compiling: " + fieldDef + " for " + sourceCode.getClassName(), e);   
  40.                 throw e;    
  41.             }    
  42.         }           
  43.          //增加方法,这里主要就是mapAtoB和mapBtoA方法   
  44.         //直接用源码通过Javassist往类“加”方法    
  45.         for (String methodDef : sourceCode.getMethods()) {    
  46.             try {    
  47.                 byteCodeClass.addMethod(CtNewMethod.make(methodDef, byteCodeClass));    
  48.             } catch (CannotCompileException e) {    
  49.                 LOG.error(    
  50.                         "An exception occured while compiling the following method:\n\n " + methodDef + "\n\n for "    
  51.                                 + sourceCode.getClassName() + "\n", e);    
  52.                 throw e;    
  53.             }            
  54.          }    
  55.         //生成类    
  56.         compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());   
  57.                //字节码文件写磁盘   
  58.         writeClassFile(sourceCode, byteCodeClass);         
  59.      } catch (NotFoundException e) {    
  60.         throw new SourceCodeGenerationException(e);    
  61.     } catch (CannotCompileException e) {    
  62.         throw new SourceCodeGenerationException("Error compiling " + sourceCode.getClassName(), e);    
  63.     } catch (IOException e) {    
  64.         throw new SourceCodeGenerationException("Could not write files for " + sourceCode.getClassName(), e);   
  65.      }       
  66.      return compiledClass;    
  67. }   

好,mapper类生成了,现在就看在调用MapperFacade的map方法是如何使用这个mapper类的。

其实很简单,还记得生成的mapper是放到mappersRegistry吗,跟踪代码,在resolveMappingStrategy方法根据typeA和typeB在mappersRegistry找到mapper,在调用mapper的mapAtoB或mapBtoA方法即可。

小结

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

总结

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

 

责任编辑:庞桂玉 来源: Java技术栈
相关推荐

2021-01-11 14:16:19

Bean代码Java

2020-11-03 14:30:02

MySQL5.7MyS8.0数据库

2023-12-11 07:37:08

mongodb经纬度性能

2023-06-20 12:02:39

WhileFor(;;)

2022-01-17 07:50:36

Maven Gradle 工具

2017-01-05 18:47:26

Windows 10操作系统微软

2011-05-07 14:15:44

工作站Quadro FX17显卡

2023-04-26 00:01:04

2015-07-29 10:28:59

JVM参数配置参数

2021-05-17 11:55:48

监控命令Linux

2021-03-16 10:20:56

监控系统架构技术

2010-07-02 14:21:02

Exchange 20

2016-10-20 23:26:23

服务器性能CPU

2023-05-16 07:35:29

2023-05-29 08:56:34

深拷贝浅拷贝对象

2023-09-14 15:48:53

排序测试

2013-05-23 15:17:35

无线接入无线接入控制器

2023-06-08 11:27:10

模型AI

2024-09-29 11:54:56

点赞
收藏

51CTO技术栈公众号