Java常见bean mapper的性能及原理分析

java部分工具类快捷使用技巧 4、PropertyUtils和BeanUtils的功能基本一致,唯一的区别是BeanUtils在对Bean赋值时会进行类型转化,而PropertyUtils不会对类型进行转化,如果类型不同则会抛出异常!建议基本类型在源目标类型一致的情况下使用PropertyUtils效率会更高。复杂类型的拷贝可以使用Dozer。3、以上是在只考虑基本类型的情况下比较的,dozer如果深拷贝或者关联拷贝时,效率应该会更慢。2、十万次以上的效率beanutils的效率和dozer差不多。................... 阅读详情

来源:http://r6d.cn/VxXn

背景

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

性能分析

其实这几个工具要做的事情很简单,而且在使用上也是类似的,所以我觉得先给大家看看性能分析的对比结果,让大家有一个大概的认识。我是使用JMH来做性能分析的,代码如下:

要复制的对象比较简单,包含了一些基本类型;有一次warmup,因为一些工具是需要“预编译”和做缓存的,这样做对比才会比较客观;分别复制1000、10000、100000个对象,这是比较常用数量级了吧。

@BenchmarkMode(Mode.AverageTime)  
@OutputTimeUnit(TimeUnit.MICROSECONDS)  
@Fork(1)  
@Warmup(iterations = 1)  
@State(Scope.Benchmark)  
public class BeanMapperBenchmark {  
  
    @Param({"1000", "10000", "100000"})  
    private int times;  
  
    private int time;  
  
    private static MapperFactory mapperFactory;  
  
    private static Mapper mapper;  
  
    static {  
        mapperFactory = new DefaultMapperFactory.Builder().build();  
        mapperFactory.classMap(SourceVO.class, TargetVO.class)  
                .byDefault()  
                .register();  
  
        mapper = DozerBeanMapperBuilder.create()  
                .withMappingBuilder(new BeanMappingBuilder() {  
                    @Override  
                    protected void configure() {  
                        mapping(SourceVO.class, TargetVO.class)  
                                .fields("fullName", "name")  
                                .exclude("in");  
                    }  
                }).build();  
    }  
  
    public static void main(String[] args) throws Exception {  
        Options options = new OptionsBuilder()  
                .include(BeanMapperBenchmark.class.getName()).measurementIterations(3)  
                .build();  
        new Runner(options).run();  
    }  
  
    @Setup  
    public void prepare() {  
        this.time = times;  
    }  
  
    @Benchmark  
    public void springBeanUtilTest(){  
        SourceVO sourceVO = getSourceVO();  
        for(int i = 0; i < time; i++){  
            TargetVO targetVO = new TargetVO();  
            BeanUtils.copyProperties(sourceVO, targetVO);  
        }  
    }  
  
    @Benchmark  
    public void apacheBeanUtilTest() throws Exception{  
        SourceVO sourceVO = getSourceVO();  
        for(int i = 0; i < time; i++){  
            TargetVO targetVO = new TargetVO();  
            org.apache.commons.beanutils.BeanUtils.copyProperties(targetVO, sourceVO);  
        }  
  
    }  
  
    @Benchmark  
    public void beanCopierTest(){  
        SourceVO sourceVO = getSourceVO();  
        for(int i = 0; i < time; i++){  
            TargetVO targetVO = new TargetVO();  
            BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);  
            bc.copy(sourceVO, targetVO, null);  
        }  
  
    }  
  
    @Benchmark  
    public void dozerTest(){  
        SourceVO sourceVO = getSourceVO();  
        for(int i = 0; i < time; i++){  
            TargetVO map = mapper.map(sourceVO, TargetVO.class);  
        }  
    }  
  
    @Benchmark  
    public void orikaTest(){  
        SourceVO sourceVO = getSourceVO();  
        for(int i = 0; i < time; i++){  
            MapperFacade mapper = mapperFactory.getMapperFacade();  
            TargetVO map = mapper.map(sourceVO, TargetVO.class);  
        }  
    }  
  
    private SourceVO getSourceVO(){  
        SourceVO sourceVO = new SourceVO();  
        sourceVO.setP1(1);  
        sourceVO.setP2(2L);  
        sourceVO.setP3(new Integer(3).byteValue());  
        sourceVO.setDate1(new Date());  
        sourceVO.setPattr1("1");  
        sourceVO.setIn(new SourceVO.Inner(1));  
        sourceVO.setFullName("alben");  
        return sourceVO;  
    }  
  
}  

在我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)

/**  
  * Copy the property values of the given source bean into the given target bean.  
  * <p>Note: The source and target classes do not have to match or even be derived  
  * from each other, as long as the properties match. Any bean properties that the  
  * source bean exposes but the target bean does not will silently be ignored.  
  * @param source the source bean  
  * @param target the target bean  
  * @param editable the class (or interface) to restrict property setting to  
  * @param ignoreProperties array of property names to ignore  
  * @throws BeansException if the copying failed  
  * @see BeanWrapper  
  */  
 private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)  
   throws BeansException {  
  
  Assert.notNull(source, "Source must not be null");  
  Assert.notNull(target, "Target must not be null");  
  
  Class<?> actualEditable = target.getClass();  
  if (editable != null) {  
   if (!editable.isInstance(target)) {  
    throw new IllegalArgumentException("Target class [" + target.getClass().getName() +  
      "] not assignable to Editable class [" + editable.getName() + "]");  
   }  
   actualEditable = editable;  
  }  
    //获取target类的属性(有缓存)  
  PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);  
  List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);  
  
  for (PropertyDescriptor targetPd : targetPds) {  
   Method writeMethod = targetPd.getWriteMethod();  
   if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {  
        //获取source类的属性(有缓存)  
    PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());  
    if (sourcePd != null) {  
     Method readMethod = sourcePd.getReadMethod();  
     if (readMethod != null &&  
              //判断target的setter方法入参和source的getter方法返回类型是否一致  
       ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {  
      try {  
       if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {  
        readMethod.setAccessible(true);  
       }  
              //获取源值  
       Object value = readMethod.invoke(source);  
       if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {  
        writeMethod.setAccessible(true);  
       }  
              //赋值到target  
       writeMethod.invoke(target, value);  
      }  
      catch (Throwable ex) {  
       throw new FatalBeanException(  
         "Could not copy property '" + targetPd.getName() + "' from source to target", ex);  
      }  
     }  
    }  
   }  
  }  
 }  

小结

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)

public void copyProperties(final Object dest, final Object orig)  
        throws IllegalAccessException, InvocationTargetException {  
          
        if (dest == null) {  
            throw new IllegalArgumentException  
                    ("No destination bean specified");  
        }  
        if (orig == null) {  
            throw new IllegalArgumentException("No origin bean specified");  
        }  
        if (log.isDebugEnabled()) {  
            log.debug("BeanUtils.copyProperties(" + dest + ", " +  
                      orig + ")");  
        }  
        // Apache Common自定义的DynaBean  
        if (orig instanceof DynaBean) {  
            final DynaProperty[] origDescriptors =  
                ((DynaBean) orig).getDynaClass().getDynaProperties();  
            for (DynaProperty origDescriptor : origDescriptors) {  
                final String name = origDescriptor.getName();  
                // Need to check isReadable() for WrapDynaBean  
                // (see Jira issue# BEANUTILS-61)  
                if (getPropertyUtils().isReadable(orig, name) &&  
                    getPropertyUtils().isWriteable(dest, name)) {  
                    final Object value = ((DynaBean) orig).get(name);  
                    copyProperty(dest, name, value);  
                }  
            }  
        // Map类型  
        } else if (orig instanceof Map) {  
            @SuppressWarnings("unchecked")  
            final  
            // Map properties are always of type <String, Object>  
            Map<String, Object> propMap = (Map<String, Object>) orig;  
            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {  
                final String name = entry.getKey();  
                if (getPropertyUtils().isWriteable(dest, name)) {  
                    copyProperty(dest, name, entry.getValue());  
                }  
            }  
        // 标准的JavaBean  
        } else {  
            final PropertyDescriptor[] origDescriptors =  
                //获取PropertyDescriptor  
                getPropertyUtils().getPropertyDescriptors(orig);  
            for (PropertyDescriptor origDescriptor : origDescriptors) {  
                final String name = origDescriptor.getName();  
                if ("class".equals(name)) {  
                    continue; // No point in trying to set an object's class  
                }  
                //是否可读和可写  
                if (getPropertyUtils().isReadable(orig, name) &&  
                    getPropertyUtils().isWriteable(dest, name)) {  
                    try {  
                        //获取源值  
                        final Object value =  
                            getPropertyUtils().getSimpleProperty(orig, name);  
                        //赋值操作  
                        copyProperty(dest, name, value);  
                    } catch (final NoSuchMethodException e) {  
                        // Should not happen  
                    }  
                }  
            }  
        }  
  
    }  

小结

Apache BeanUtils的实现跟Spring BeanUtils总体上类似,但是性能却低很多,这个可以从上面性能比较看出来。阿里的Java规范是不建议使用的。

BeanCopier

使用

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

@Test  
    public void beanCopierSimpleTest() {  
        SourceVO sourceVO = getSourceVO();  
        log.info("source={}", GsonUtil.toJson(sourceVO));  
        TargetVO targetVO = new TargetVO();  
        BeanCopier bc = BeanCopier.create(SourceVO.class, TargetVO.class, false);  
        bc.copy(sourceVO, targetVO, null);  
        log.info("target={}", GsonUtil.toJson(targetVO));  
    }  

只需要预先定义好要转换的source类和target类就好了,可以选择是否使用Converter,这个下面会说到。

在上面的性能测试中,BeanCopier是所有中表现最好的,那么我们分析一下它的实现原理。

原理

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

get/set。

BeanCopier.create方法

public static BeanCopier create(Class source, Class target, boolean useConverter) {  
        Generator gen = new Generator();  
        gen.setSource(source);  
        gen.setTarget(target);  
        gen.setUseConverter(useConverter);  
        return gen.create();  
    }  
  
public BeanCopier create() {  
            Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);  
            return (BeanCopier)super.create(key);  
        }  

这里的意思是用KEY_FACTORY创建一个BeanCopier出来,然后调用create方法来生成字节码。

KEY_FACTORY其实就是用cglib通过BeanCopierKey接口生成出来的一个类

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

通过设置

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

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

下面是KEY_FACTORY的类

public class BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd extends KeyFactory implements BeanCopierKey {  
    private final String FIELD_0;  
    private final String FIELD_1;  
    private final boolean FIELD_2;  
  
    public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd() {  
    }  
  
    public Object newInstance(String var1, String var2, boolean var3) {  
        return new BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(var1, var2, var3);  
    }  
  
    public BeanCopier$BeanCopierKey$$KeyFactoryByCGLIB$$f32401fd(String var1, String var2, boolean var3) {  
        this.FIELD_0 = var1;  
        this.FIELD_1 = var2;  
        this.FIELD_2 = var3;  
    }  
    //省去hashCode等方法。。。  
}  

继续跟踪Generator.create方法,由于Generator是继承AbstractClassGenerator,这个AbstractClassGenerator是cglib用来生成字节码的一个模板类,Generator的super.create其实调用

AbstractClassGenerator的create方法,最终会调用到Generator的模板方法generateClass方法,我们不去细究AbstractClassGenerator的细节,重点看generateClass。

这个是一个生成java类的方法,理解起来就好像我们平时写代码一样。

public void generateClass(ClassVisitor v) {  
            Type sourceType = Type.getType(source);  
            Type targetType = Type.getType(target);  
            ClassEmitter ce = new ClassEmitter(v);  
            //开始“写”类,这里有修饰符、类名、父类等信息  
            ce.begin_class(Constants.V1_2,  
                           Constants.ACC_PUBLIC,  
                           getClassName(),  
                           BEAN_COPIER,  
                           null,  
                           Constants.SOURCE_FILE);  
            //没有构造方法  
            EmitUtils.null_constructor(ce);  
            //开始“写”一个方法,方法名是copy  
            CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);  
            //通过Introspector获取source类和target类的PropertyDescriptor  
            PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);  
            PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);  
              
            Map names = new HashMap();  
            for (int i = 0; i < getters.length; i++) {  
                names.put(getters[i].getName(), getters[i]);  
            }  
            Local targetLocal = e.make_local();  
            Local sourceLocal = e.make_local();  
            if (useConverter) {  
                e.load_arg(1);  
                e.checkcast(targetType);  
                e.store_local(targetLocal);  
                e.load_arg(0);                  
                e.checkcast(sourceType);  
                e.store_local(sourceLocal);  
            } else {  
                e.load_arg(1);  
                e.checkcast(targetType);  
                e.load_arg(0);  
                e.checkcast(sourceType);  
            }  
            //通过属性名来生成转换的代码  
            //以setter作为遍历  
            for (int i = 0; i < setters.length; i++) {  
                PropertyDescriptor setter = setters[i];  
                //根据setter的name获取getter  
                PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());  
                if (getter != null) {  
                    //获取读写方法  
                    MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());  
                    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());  
                    //如果用了useConverter,则进行下面的拼装代码方式  
                    if (useConverter) {  
                        Type setterType = write.getSignature().getArgumentTypes()[0];  
                        e.load_local(targetLocal);  
                        e.load_arg(2);  
                        e.load_local(sourceLocal);  
                        e.invoke(read);  
                        e.box(read.getSignature().getReturnType());  
                        EmitUtils.load_class(e, setterType);  
                        e.push(write.getSignature().getName());  
                        e.invoke_interface(CONVERTER, CONVERT);  
                        e.unbox_or_zero(setterType);  
                        e.invoke(write);  
                      //compatible用来判断getter和setter是否类型一致  
                    } else if (compatible(getter, setter)) {  
                        e.dup2();  
                        e.invoke(read);  
                        e.invoke(write);  
                    }  
                }  
            }  
            e.return_value();  
            e.end_method();  
            ce.end_class();  
        }  
  
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {  
            // TODO: allow automatic widening conversions?  
            return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());  
        }  

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

public class Object$$BeanCopierByCGLIB$$d1d970c8 extends BeanCopier {  
    public Object$$BeanCopierByCGLIB$$d1d970c8() {  
    }  
  
    public void copy(Object var1, Object var2, Converter var3) {  
        TargetVO var10000 = (TargetVO)var2;  
        SourceVO var10001 = (SourceVO)var1;  
        var10000.setDate1(((SourceVO)var1).getDate1());  
        var10000.setIn(var10001.getIn());  
        var10000.setListData(var10001.getListData());  
        var10000.setMapData(var10001.getMapData());  
        var10000.setP1(var10001.getP1());  
        var10000.setP2(var10001.getP2());  
        var10000.setP3(var10001.getP3());  
        var10000.setPattr1(var10001.getPattr1());  
    }  
}  

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

再看看使用useConverter的情况:

public class Object$$BeanCopierByCGLIB$$d1d970c7 extends BeanCopier {  
    private static final Class CGLIB$load_class$java$2Eutil$2EDate;  
    private static final Class CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner;  
    private static final Class CGLIB$load_class$java$2Eutil$2EList;  
    private static final Class CGLIB$load_class$java$2Eutil$2EMap;  
    private static final Class CGLIB$load_class$java$2Elang$2EInteger;  
    private static final Class CGLIB$load_class$java$2Elang$2ELong;  
    private static final Class CGLIB$load_class$java$2Elang$2EByte;  
    private static final Class CGLIB$load_class$java$2Elang$2EString;  
  
    public Object$$BeanCopierByCGLIB$$d1d970c7() {  
    }  
  
    public void copy(Object var1, Object var2, Converter var3) {  
        TargetVO var4 = (TargetVO)var2;  
        SourceVO var5 = (SourceVO)var1;  
        var4.setDate1((Date)var3.convert(var5.getDate1(), CGLIB$load_class$java$2Eutil$2EDate, "setDate1"));  
        var4.setIn((Inner)var3.convert(var5.getIn(), CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner, "setIn"));  
        var4.setListData((List)var3.convert(var5.getListData(), CGLIB$load_class$java$2Eutil$2EList, "setListData"));  
        var4.setMapData((Map)var3.convert(var5.getMapData(), CGLIB$load_class$java$2Eutil$2EMap, "setMapData"));  
        var4.setP1((Integer)var3.convert(var5.getP1(), CGLIB$load_class$java$2Elang$2EInteger, "setP1"));  
        var4.setP2((Long)var3.convert(var5.getP2(), CGLIB$load_class$java$2Elang$2ELong, "setP2"));  
        var4.setP3((Byte)var3.convert(var5.getP3(), CGLIB$load_class$java$2Elang$2EByte, "setP3"));  
        var4.setPattr1((String)var3.convert(var5.getPattr1(), CGLIB$load_class$java$2Elang$2EString, "setPattr1"));  
        var4.setSeq((Long)var3.convert(var5.getSeq(), CGLIB$load_class$java$2Elang$2ELong, "setSeq"));  
    }  
  
    static void CGLIB$STATICHOOK1() {  
        CGLIB$load_class$java$2Eutil$2EDate = Class.forName("java.util.Date");  
        CGLIB$load_class$beanmapper_compare$2Evo$2ESourceVO$24Inner = Class.forName("beanmapper_compare.vo.SourceVO$Inner");  
        CGLIB$load_class$java$2Eutil$2EList = Class.forName("java.util.List");  
        CGLIB$load_class$java$2Eutil$2EMap = Class.forName("java.util.Map");  
        CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");  
        CGLIB$load_class$java$2Elang$2ELong = Class.forName("java.lang.Long");  
        CGLIB$load_class$java$2Elang$2EByte = Class.forName("java.lang.Byte");  
        CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");  
    }  
  
    static {  
        CGLIB$STATICHOOK1();  
    }  
}  

小结

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和注解的配置,请看官方文档。

private Mapper dozerMapper;  
  
    @Before  
    public void setup(){  
        dozerMapper = DozerBeanMapperBuilder.create()  
                .withMappingBuilder(new BeanMappingBuilder() {  
                    @Override  
                    protected void configure() {  
                        mapping(SourceVO.class, TargetVO.class)  
                                .fields("fullName", "name")  
                                .exclude("in");  
                    }  
                })  
                .withCustomConverter(null)  
                .withEventListener(null)  
                .build();  
    }  
      
    @Test  
    public void dozerTest(){  
        SourceVO sourceVO = getSourceVO();  
        log.info("sourceVO={}", GsonUtil.toJson(sourceVO));  
        TargetVO map = dozerMapper.map(sourceVO, TargetVO.class);  
        log.info("map={}", GsonUtil.toJson(map));  
    }  

原理

Dozer的实现原理本质上还是用反射/Introspector那套,但是其丰富的功能,以及支持多种实现方式(API、XML、注解)使得代码看上去有点复杂,在翻阅代码时,我们大可不必理会这些类,只需要知道它们大体的作用就行了,重点关注核心流程和代码的实现。下面我们重点看看构建mapper的build方法和实现映射的map方法。

build方法很简单,它是一个初始化的动作,就是通过用户的配置来构建出一系列后面要用到的配置对象、上下文对象,或其他封装对象,我们不必深究这些对象是怎么实现的,从名字上我们大概能猜出这些对象是干嘛,负责什么就可以了。

DozerBeanMapper(List<String> mappingFiles,  
                    BeanContainer beanContainer,  
                    DestBeanCreator destBeanCreator,  
                    DestBeanBuilderCreator destBeanBuilderCreator,  
                    BeanMappingGenerator beanMappingGenerator,  
                    PropertyDescriptorFactory propertyDescriptorFactory,  
                    List<CustomConverter> customConverters,  
                    List<MappingFileData> mappingsFileData,  
                    List<EventListener> eventListeners,  
                    CustomFieldMapper customFieldMapper,  
                    Map<String, CustomConverter> customConvertersWithId,  
                    ClassMappings customMappings,  
                    Configuration globalConfiguration,  
                    CacheManager cacheManager) {  
        this.beanContainer = beanContainer;  
        this.destBeanCreator = destBeanCreator;  
        this.destBeanBuilderCreator = destBeanBuilderCreator;  
        this.beanMappingGenerator = beanMappingGenerator;  
        this.propertyDescriptorFactory = propertyDescriptorFactory;  
        this.customConverters = new ArrayList<>(customConverters);  
        this.eventListeners = new ArrayList<>(eventListeners);  
        this.mappingFiles = new ArrayList<>(mappingFiles);  
        this.customFieldMapper = customFieldMapper;  
        this.customConvertersWithId = new HashMap<>(customConvertersWithId);  
        this.eventManager = new DefaultEventManager(eventListeners);  
        this.customMappings = customMappings;  
        this.globalConfiguration = globalConfiguration;  
        this.cacheManager = cacheManager;  
    }  

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

private <T> T mapGeneral(Object srcObj, final Class<T> destClass, final T destObj, final String mapId) {  
        srcObj = MappingUtils.deProxy(srcObj, beanContainer);  
  
        Class<T> destType;  
        T result;  
        if (destClass == null) {  
            destType = (Class<T>)destObj.getClass();  
            result = destObj;  
        } else {  
            destType = destClass;  
            result = null;  
        }  
  
        ClassMap classMap = null;  
        try {  
            //构建ClassMap  
            //ClassMap是包括src类和dest类和其他配置的一个封装  
            classMap = getClassMap(srcObj.getClass(), destType, mapId);  
  
            //注册事件  
            eventManager.on(new DefaultEvent(EventTypes.MAPPING_STARTED, classMap, null, srcObj, result, null));  
  
              
            //看看有没有自定义converter  
            Class<?> converterClass = MappingUtils.findCustomConverter(converterByDestTypeCache, classMap.getCustomConverters(), srcObj  
                    .getClass(), destType);  
  
            if (destObj == null) {  
                // If this is a nested MapperAware conversion this mapping can be already processed  
                // but we can do this optimization only in case of no destObject, instead we must copy to the dest object  
                Object alreadyMappedValue = mappedFields.getMappedValue(srcObj, destType, mapId);  
                if (alreadyMappedValue != null) {  
                    return (T)alreadyMappedValue;  
                }  
            }  
            //优先使用自定义converter进行映射  
            if (converterClass != null) {  
                return (T)mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);  
            }  
  
            //也是对配置进行了封装  
            BeanCreationDirective creationDirective =  
                    new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType,  
                                              classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod(),  
                                              classMap.getDestClass().isSkipConstructor());  
            //继续进行映射  
            result = createByCreationDirectiveAndMap(creationDirective, classMap, srcObj, result, false, null);  
        } catch (Throwable e) {  
            MappingUtils.throwMappingException(e);  
        }  
        eventManager.on(new DefaultEvent(EventTypes.MAPPING_FINISHED, classMap, null, srcObj, result, null));  
  
        return result;  
    }  

一般情况下createByCreationDirectiveAndMap方法会一直调用到mapFromFieldMap方法,而在没有自定义converter的情况下会调用mapOrRecurseObject方法

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

private Object mapOrRecurseObject(Object srcObj, Object srcFieldValue, Class<?> destFieldType, FieldMap fieldMap, Object destObj) {  
        Class<?> srcFieldClass = srcFieldValue != null ? srcFieldValue.getClass() : fieldMap.getSrcFieldType(srcObj.getClass());  
        Class<?> converterClass = MappingUtils.determineCustomConverter(fieldMap, converterByDestTypeCache, fieldMap.getClassMap()  
                .getCustomConverters(), srcFieldClass, destFieldType);  
  
        //自定义converter的处理  
        if (converterClass != null) {  
            return mapUsingCustomConverter(converterClass, srcFieldClass, srcFieldValue, destFieldType, destObj, fieldMap, false);  
        }  
  
        if (srcFieldValue == null) {  
            return null;  
        }  
  
        String srcFieldName = fieldMap.getSrcFieldName();  
        String destFieldName = fieldMap.getDestFieldName();  
          
        if (!(DozerConstants.SELF_KEYWORD.equals(srcFieldName) && DozerConstants.SELF_KEYWORD.equals(destFieldName))) {  
            Object alreadyMappedValue = mappedFields.getMappedValue(srcFieldValue, destFieldType, fieldMap.getMapId());  
            if (alreadyMappedValue != null) {  
                return alreadyMappedValue;  
            }  
        }  
  
        //如果只是浅拷贝则直接返回(可配置)  
        if (fieldMap.isCopyByReference()) {  
            // just get the src and return it, no transformation.  
            return srcFieldValue;  
        }  
  
        //对Map类型的处理  
        boolean isSrcFieldClassSupportedMap = MappingUtils.isSupportedMap(srcFieldClass);  
        boolean isDestFieldTypeSupportedMap = MappingUtils.isSupportedMap(destFieldType);  
        if (isSrcFieldClassSupportedMap && isDestFieldTypeSupportedMap) {  
            return mapMap(srcObj, (Map<?, ?>)srcFieldValue, fieldMap, destObj);  
        }  
        if (fieldMap instanceof MapFieldMap && destFieldType.equals(Object.class)) {  
              
            destFieldType = fieldMap.getDestHintContainer() != null ? fieldMap.getDestHintContainer().getHint() : srcFieldClass;  
        }  
  
        //对基本类型的映射处理  
        //PrimitiveOrWrapperConverter类支持兼容了基本类型之间的互相转换  
        if (primitiveConverter.accepts(srcFieldClass) || primitiveConverter.accepts(destFieldType)) {  
            // Primitive or Wrapper conversion  
            if (fieldMap.getDestHintContainer() != null) {  
                Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());  
                // if the destType is null this means that there was more than one hint.  
                // we must have already set the destType then.  
                if (destHintType != null) {  
                    destFieldType = destHintType;  
                }  
            }  
  
            //#1841448 - if trim-strings=true, then use a trimmed src string value when converting to dest value  
            Object convertSrcFieldValue = srcFieldValue;  
            if (fieldMap.isTrimStrings() && srcFieldValue.getClass().equals(String.class)) {  
                convertSrcFieldValue = ((String)srcFieldValue).trim();  
            }  
  
            DateFormatContainer dfContainer = new DateFormatContainer(fieldMap.getDateFormat());  
  
            if (fieldMap instanceof MapFieldMap && !primitiveConverter.accepts(destFieldType)) {  
                  
                return primitiveConverter.convert(convertSrcFieldValue, convertSrcFieldValue.getClass(), dfContainer);  
            } else {  
                return primitiveConverter.convert(convertSrcFieldValue, destFieldType, dfContainer, destFieldName, destObj);  
            }  
        }  
        //对集合类型的映射处理  
        if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {  
            return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);  
        }  
  
        //对枚举类型的映射处理  
        if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {  
            return mapEnum((Enum)srcFieldValue, (Class<Enum>)destFieldType);  
        }  
        if (fieldMap.getDestDeepIndexHintContainer() != null) {  
            destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();  
        }  
        //其他复杂对象类型的处理  
        return mapCustomObject(fieldMap, destObj, destFieldType, destFieldName, srcFieldValue);  
    }  

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

private Object mapCustomObject(FieldMap fieldMap, Object destObj, Class<?> destFieldType, String destFieldName, Object srcFieldValue) {  
        srcFieldValue = MappingUtils.deProxy(srcFieldValue, beanContainer);  
  
        // Custom java bean. Need to make sure that the destination object is not  
        // already instantiated.  
        Object result = null;  
        // in case of iterate feature new objects are created in any case  
        if (!DozerConstants.ITERATE.equals(fieldMap.getDestFieldType())) {  
            result = getExistingValue(fieldMap, destObj, destFieldType);  
        }  
  
        // if the field is not null than we don't want a new instance  
        if (result == null) {  
            // first check to see if this plain old field map has hints to the actual  
            // type.  
            if (fieldMap.getDestHintContainer() != null) {  
                Class<?> destHintType = fieldMap.getDestHintType(srcFieldValue.getClass());  
                // if the destType is null this means that there was more than one hint.  
                // we must have already set the destType then.  
                if (destHintType != null) {  
                    destFieldType = destHintType;  
                }  
            }  
            // Check to see if explicit map-id has been specified for the field  
            // mapping  
            String mapId = fieldMap.getMapId();  
  
            Class<?> targetClass;  
            if (fieldMap.getDestHintContainer() != null && fieldMap.getDestHintContainer().getHint() != null) {  
                targetClass = fieldMap.getDestHintContainer().getHint();  
            } else {  
                targetClass = destFieldType;  
            }  
            ClassMap classMap = getClassMap(srcFieldValue.getClass(), targetClass, mapId);  
  
            BeanCreationDirective creationDirective = new BeanCreationDirective(srcFieldValue, classMap.getSrcClassToMap(), classMap.getDestClassToMap(),  
                                                                                destFieldType, classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(),  
                                                                                fieldMap.getDestFieldCreateMethod() != null ? fieldMap.getDestFieldCreateMethod() :  
                                                                                        classMap.getDestClassCreateMethod(),  
                                                                                classMap.getDestClass().isSkipConstructor(), destObj, destFieldName);  
  
            result = createByCreationDirectiveAndMap(creationDirective, classMap, srcFieldValue, null, false, fieldMap.getMapId());  
        } else {  
            mapToDestObject(null, srcFieldValue, result, false, fieldMap.getMapId());  
        }  
  
        return result;  
    }  

小结

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

Orika

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

使用

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

private MapperFactory mapperFactory;  
  
@Before  
public void setup() {  
    mapperFactory = new DefaultMapperFactory.Builder().build();  
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();  
    converterFactory.registerConverter(new TypeConverter());  
    mapperFactory.classMap(SourceVO.class, TargetVO.class)  
            .field("fullName", "name")  
            .field("type", "enumType")  
            .exclude("in")  
            .byDefault()  
            .register();  
}  
  
@Test  
public void main() {  
    MapperFacade mapper = mapperFactory.getMapperFacade();  
    SourceVO sourceVO = getSourceVO();  
    log.info("sourceVO={}", GsonUtil.toJson(sourceVO));  
    TargetVO map = mapper.map(sourceVO, TargetVO.class);  
    log.info("map={}", GsonUtil.toJson(map));  
}  

原理

在讲解实现原理时,我们先看看Orika在背后干了什么事情。

通过增加以下配置,我们可以看到Orika在做映射过程中生成mapper的源码和字节码。

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

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

package ma.glasnost.orika.generated;  
  
public class Orika_TargetVO_SourceVO_Mapper947163525829122$0 extends ma.glasnost.orika.impl.GeneratedMapperBase {  
  
 public void mapAtoB(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {  
  
  
super.mapAtoB(a, b, mappingContext);  
  
  
// sourceType: SourceVO  
beanmapper_compare.vo.SourceVO source = ((beanmapper_compare.vo.SourceVO)a);   
// destinationType: TargetVO  
beanmapper_compare.vo.TargetVO destination = ((beanmapper_compare.vo.TargetVO)b);   
  
  
destination.setName(((java.lang.String)source.getFullName()));   
if ( !(((java.lang.Integer)source.getType()) == null)){   
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)));   
} else {   
destination.setEnumType(null);  
 }  
if ( !(((java.util.Date)source.getDate1()) == null)){   
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)));   
} else {   
destination.setDate1(null);  
 }if ( !(((java.util.List)source.getListData()) == null)) {  
  
java.util.List new_listData = ((java.util.List)new java.util.ArrayList());   
  
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));   
destination.setListData(new_listData);   
} else {  
 if ( !(((java.util.List)destination.getListData()) == null)) {  
destination.setListData(null);  
};  
}if ( !(((java.util.Map)source.getMapData()) == null)){  
  
java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());   
for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {   
  
java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());   
java.lang.Integer newMapDataKey = null;   
java.util.List newMapDataVal = null;   
if ( !(((java.lang.Long)sourceMapDataEntry.getKey()) == null)){   
newMapDataKey = ((java.lang.Integer)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Long)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[3]), mappingContext));   
} else {   
newMapDataKey = null;  
 }  
if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {  
  
java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());   
  
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));   
newMapDataVal = new_newMapDataVal;   
} else {  
 if ( !(newMapDataVal == null)) {  
newMapDataVal = null;  
};  
}  
new_mapData.put(newMapDataKey, newMapDataVal);   
  
}  
destination.setMapData(new_mapData);   
} else {  
 destination.setMapData(null);  
}  
destination.setP1(((java.lang.Integer)source.getP1()));   
destination.setP2(((java.lang.Long)source.getP2()));   
destination.setP3(((java.lang.Byte)source.getP3()));   
destination.setPattr1(((java.lang.String)source.getPattr1()));   
if ( !(((java.lang.String)source.getSeq()) == null)){   
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)));   
} else {   
destination.setSeq(null);  
 }  
  if(customMapper != null) {   
    customMapper.mapAtoB(source, destination, mappingContext);  
  }  
 }  
  
 public void mapBtoA(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) {  
  
  
super.mapBtoA(a, b, mappingContext);  
  
  
// sourceType: TargetVO  
beanmapper_compare.vo.TargetVO source = ((beanmapper_compare.vo.TargetVO)a);   
// destinationType: SourceVO  
beanmapper_compare.vo.SourceVO destination = ((beanmapper_compare.vo.SourceVO)b);   
  
  
destination.setFullName(((java.lang.String)source.getName()));   
if ( !(((beanmapper_compare.vo.TargetVO.EnumType)source.getEnumType()) == null)){   
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)));   
} else {   
destination.setType(null);  
 }  
if ( !(((java.util.Date)source.getDate1()) == null)){   
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)));   
} else {   
destination.setDate1(null);  
 }if ( !(((java.util.List)source.getListData()) == null)) {  
  
java.util.List new_listData = ((java.util.List)new java.util.ArrayList());   
  
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));   
destination.setListData(new_listData);   
} else {  
 if ( !(((java.util.List)destination.getListData()) == null)) {  
destination.setListData(null);  
};  
}if ( !(((java.util.Map)source.getMapData()) == null)){  
  
java.util.Map new_mapData = ((java.util.Map)new java.util.LinkedHashMap());   
for( java.util.Iterator mapData_$_iter = ((java.util.Map)source.getMapData()).entrySet().iterator(); mapData_$_iter.hasNext(); ) {   
  
java.util.Map.Entry sourceMapDataEntry = ((java.util.Map.Entry)mapData_$_iter.next());   
java.lang.Long newMapDataKey = null;   
java.util.List newMapDataVal = null;   
if ( !(((java.lang.Integer)sourceMapDataEntry.getKey()) == null)){   
newMapDataKey = ((java.lang.Long)((ma.glasnost.orika.Converter)usedConverters[2]).convert(((java.lang.Integer)sourceMapDataEntry.getKey()), ((ma.glasnost.orika.metadata.Type)usedTypes[2]), mappingContext));   
} else {   
newMapDataKey = null;  
 }  
if ( !(((java.util.List)sourceMapDataEntry.getValue()) == null)) {  
  
java.util.List new_newMapDataVal = ((java.util.List)new java.util.ArrayList());   
  
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));   
newMapDataVal = new_newMapDataVal;   
} else {  
 if ( !(newMapDataVal == null)) {  
newMapDataVal = null;  
};  
}  
new_mapData.put(newMapDataKey, newMapDataVal);   
  
}  
destination.setMapData(new_mapData);   
} else {  
 destination.setMapData(null);  
}  
destination.setP1(((java.lang.Integer)source.getP1()));   
destination.setP2(((java.lang.Long)source.getP2()));   
destination.setP3(((java.lang.Byte)source.getP3()));   
destination.setPattr1(((java.lang.String)source.getPattr1()));   
if ( !(((java.lang.Long)source.getSeq()) == null)){   
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)));   
} else {   
destination.setSeq(null);  
 }  
  if(customMapper != null) {   
    customMapper.mapBtoA(source, destination, mappingContext);  
  }  
 }  
  
}  

这个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)

public MapperFacade getMapperFacade() {  
        if (!isBuilt) {  
            synchronized (mapperFacade) {  
                if (!isBuilt) {  
                    build();  
                }  
            }  
        }  
        return mapperFacade;  
    }  

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

public synchronized void build() {  
          
        if (!isBuilding && !isBuilt) {  
            isBuilding = true;  
              
            MappingContext context = contextFactory.getContext();  
            try {  
                if (useBuiltinConverters) {  
                    BuiltinConverters.register(converterFactory);  
                }  
                converterFactory.setMapperFacade(mapperFacade);  
                  
                for (Map.Entry<MapperKey, ClassMap<Object, Object>> classMapEntry : classMapRegistry.entrySet()) {  
                    ClassMap<Object, Object> classMap = classMapEntry.getValue();  
                    if (classMap.getUsedMappers().isEmpty()) {  
                        classMapEntry.setValue(classMap.copyWithUsedMappers(discoverUsedMappers(classMap)));  
                    }  
                }  
  
                buildClassMapRegistry();  
                  
                Map<ClassMap<?, ?>, GeneratedMapperBase> generatedMappers = new HashMap<ClassMap<?, ?>, GeneratedMapperBase>();  
                //重点看这里  
                //在使用mapperFactory配置classMap时,会存放在classMapRegistry里  
                for (ClassMap<?, ?> classMap : classMapRegistry.values()) {  
                    //对每个classMap生成一个mapper,重点看buildMapper方法  
                    generatedMappers.put(classMap, buildMapper(classMap, false, context));  
                }  
                  
                Set<Entry<ClassMap<?, ?>, GeneratedMapperBase>> generatedMapperEntries = generatedMappers.entrySet();  
                for (Entry<ClassMap<?, ?>, GeneratedMapperBase> generatedMapperEntry : generatedMapperEntries) {  
                    buildObjectFactories(generatedMapperEntry.getKey(), context);  
                    initializeUsedMappers(generatedMapperEntry.getValue(), generatedMapperEntry.getKey(), context);  
                }  
                  
            } finally {  
                contextFactory.release(context);  
            }  
              
            isBuilt = true;  
            isBuilding = false;  
        }  
    }  
      
    public Set<ClassMap<Object, Object>> lookupUsedClassMap(MapperKey mapperKey) {  
        Set<ClassMap<Object, Object>> usedClassMapSet = usedMapperMetadataRegistry.get(mapperKey);  
        if (usedClassMapSet == null) {  
            usedClassMapSet = Collections.emptySet();  
        }  
        return usedClassMapSet;  
    }  

跟踪buildMapper方法

private GeneratedMapperBase buildMapper(ClassMap<?, ?> classMap, boolean isAutoGenerated, MappingContext context) {  
          
        register(classMap.getAType(), classMap.getBType(), isAutoGenerated);  
        register(classMap.getBType(), classMap.getAType(), isAutoGenerated);  
          
        final MapperKey mapperKey = new MapperKey(classMap.getAType(), classMap.getBType());  
        //调用mapperGenerator的build方法生成mapper  
        final GeneratedMapperBase mapper = mapperGenerator.build(classMap, context);  
        mapper.setMapperFacade(mapperFacade);  
        mapper.setFromAutoMapping(isAutoGenerated);  
        if (classMap.getCustomizedMapper() != null) {  
            final Mapper<Object, Object> customizedMapper = (Mapper<Object, Object>) classMap.getCustomizedMapper();  
            mapper.setCustomMapper(customizedMapper);  
        }  
        mappersRegistry.remove(mapper);  
        //生成的mapper存放到mappersRegistry  
        mappersRegistry.add(mapper);  
        classMapRegistry.put(mapperKey, (ClassMap<Object, Object>) classMap);  
          
        return mapper;  
    }  

MapperGenerator的build方法

public GeneratedMapperBase build(ClassMap<?, ?> classMap, MappingContext context) {  
      
    StringBuilder logDetails = null;  
    try {  
        compilerStrategy.assureTypeIsAccessible(classMap.getAType().getRawType());  
        compilerStrategy.assureTypeIsAccessible(classMap.getBType().getRawType());  
          
        if (LOGGER.isDebugEnabled()) {  
            logDetails = new StringBuilder();  
            String srcName = TypeFactory.nameOf(classMap.getAType(), classMap.getBType());  
            String dstName = TypeFactory.nameOf(classMap.getBType(), classMap.getAType());  
            logDetails.append("Generating new mapper for (" + srcName + ", " + dstName + ")");  
        }  
          
        //构建用来生成源码及字节码的上下文  
        final SourceCodeContext mapperCode = new SourceCodeContext(classMap.getMapperClassName(), GeneratedMapperBase.class, context,  
                logDetails);  
          
        Set<FieldMap> mappedFields = new LinkedHashSet<FieldMap>();  
        //增加mapAtoB方法  
        mappedFields.addAll(addMapMethod(mapperCode, true, classMap, logDetails));  
        //增加mapBtoA方法  
        //addMapMethod方法基本就是手写代码的过程,有兴趣的读者可以看看  
        mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails));  
          
        //生成一个mapper实例  
        GeneratedMapperBase instance = mapperCode.getInstance();  
        instance.setAType(classMap.getAType());  
        instance.setBType(classMap.getBType());  
        instance.setFavorsExtension(classMap.favorsExtension());  
          
        if (logDetails != null) {  
            LOGGER.debug(logDetails.toString());  
            logDetails = null;  
        }  
          
        classMap = classMap.copy(mappedFields);  
        context.registerMapperGeneration(classMap);  
          
        return instance;  
          
    } catch (final Exception e) {  
        if (logDetails != null) {  
            logDetails.append("\n<---- ERROR occurred here");  
            LOGGER.debug(logDetails.toString());  
        }  
        throw new MappingException(e);  
    }  

生成mapper实例

T instance = (T) compileClass().newInstance();  
  
protected Class<?> compileClass() throws SourceCodeGenerationException {  
        try {  
            return compilerStrategy.compileClass(this);  
        } catch (SourceCodeGenerationException e) {  
            throw e;  
        }  
    }  

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

JavassistCompilerStrategy的compileClass方法

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

public Class<?> compileClass(SourceCodeContext sourceCode) throws SourceCodeGenerationException {  
          
        StringBuilder className = new StringBuilder(sourceCode.getClassName());  
        CtClass byteCodeClass = null;  
        int attempts = 0;  
        Random rand = RANDOM;  
        while (byteCodeClass == null) {  
            try {  
                //创建一个类  
                byteCodeClass = classPool.makeClass(className.toString());  
            } catch (RuntimeException e) {  
                if (attempts < 5) {  
                    className.append(Integer.toHexString(rand.nextInt()));  
                } else {  
                    // No longer likely to be accidental name collision;  
                    // propagate the error  
                    throw e;  
                }  
            }  
        }  
          
        CtClass abstractMapperClass;  
        Class<?> compiledClass;  
          
        try {  
            //把源码写到磁盘(通过上面提到的配置)  
            writeSourceFile(sourceCode);  
              
            Boolean existing = superClasses.put(sourceCode.getSuperClass(), true);  
            if (existing == null || !existing) {  
                classPool.insertClassPath(new ClassClassPath(sourceCode.getSuperClass()));  
            }  
              
            if (registerClassLoader(Thread.currentThread().getContextClassLoader())) {  
                classPool.insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));  
            }  
              
            abstractMapperClass = classPool.get(sourceCode.getSuperClass().getCanonicalName());  
            byteCodeClass.setSuperclass(abstractMapperClass);  
              
            //增加字段  
            for (String fieldDef : sourceCode.getFields()) {  
                try {  
                    byteCodeClass.addField(CtField.make(fieldDef, byteCodeClass));  
                } catch (CannotCompileException e) {  
                    LOG.error("An exception occurred while compiling: " + fieldDef + " for " + sourceCode.getClassName(), e);  
                    throw e;  
                }  
            }  
              
            //增加方法,这里主要就是mapAtoB和mapBtoA方法  
            //直接用源码通过Javassist往类“加”方法  
            for (String methodDef : sourceCode.getMethods()) {  
                try {  
                    byteCodeClass.addMethod(CtNewMethod.make(methodDef, byteCodeClass));  
                } catch (CannotCompileException e) {  
                    LOG.error(  
                            "An exception occured while compiling the following method:\n\n " + methodDef + "\n\n for "  
                                    + sourceCode.getClassName() + "\n", e);  
                    throw e;  
                }  
                  
            }  
            //生成类  
            compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());  
              
            //字节码文件写磁盘  
            writeClassFile(sourceCode, byteCodeClass);  
              
        } catch (NotFoundException e) {  
            throw new SourceCodeGenerationException(e);  
        } catch (CannotCompileException e) {  
            throw new SourceCodeGenerationException("Error compiling " + sourceCode.getClassName(), e);  
        } catch (IOException e) {  
            throw new SourceCodeGenerationException("Could not write files for " + sourceCode.getClassName(), e);  
        }  
          
        return compiledClass;  
    }  

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

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

小结

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

总结

通过对BeanUtils、BeanCopier、Dozer、Orika这几个工具的对比,我们得知了它们的性能以及实现原理。在使用时,我们可以根据自己的实际情况选择,推荐使用Orika。

参考资料

打开 orika 的正确方式

IntelliJ IDEA 2024.1 解决 MyBatis Mapper 注入报红:6种方案原理与适用场景对比 本文详细解析了IntelliJ IDEA 2024.1中MyBatis Mapper注入报红问题的6种解决方案,包括@Autowired(required=false)、@Resource替换、@Repository注解、Lombok构造器注入、调整IDEA检查设置和安装MyBatis插件。通过对比各方案的原理与适用场景,帮助开发者根据项目需求选择最佳实践,提升开发效率与代码质量。 阅读详情

相关推荐

IDEA 2024.1 解决 MyBatis Mapper 注入报红:6种方案性能与兼容性对比

本文详细解析了IDEA 2024.1中MyBatis Mapper注入报红的6种解决方案,包括@Autowired(required=false)、@Resource替换、@Repository注解等,对比了各方案的性能与兼容性。针对不同项目需求,提供了从快速修复到终极解决方案的完整指南,帮助开发者提升编码效率和团队协作质量。

cneo2012的博客 434

java对象复制

原文链接:https://blog.youkuaiyun.com/54powerman/article/details/64920431?locationNum=6&fps=1 java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的: @Test public void testassign(){ Person p1=new Person(); p1...

u010730870的博客 548

别再手动算了!用Excel函数一键搞定AQI和首要污染物分析(附完整公式模板)

本文介绍如何利用Excel函数自动化计算AQI和首要污染物分析,大幅提升数据处理效率。通过构建标准化输入模板、智能计算IAQI、自动判定AQI与首要污染物,并生成可视化仪表盘,帮助环保工作者快速完成每日监测报告。

weixin_30443075的博客 346

java bean转换工具比较

java bean的转换工具目前主要有两大类,一类是通过反射实现的,如BeanUtils,一类是通过直接映射字节码实现的,如BeanCopier、Orika等。个人觉得这两类在性能上根本没有可比性。但是项目中总有人对BeanUtils这个工具类有种莫名的执着,因此本文将从bean转换的正确性、时间效率和易用性三个方面来比较BeanUtils、BeanCopier和Orika。 定义一个稍复杂点儿的...

weixin_36142042的博客 3614

科研笔记:怎样写好博士论文?

博士论文外审避坑指南与写作要点 博士论文外审的核心关注点呈现5级优先级:科研成果(硬指标)>创新性>逻辑结构与场景合理性>文献综述与规范性>工作量验证。外审专家会重点核查:1)成果清单是否达标;2)创新点是否真实明确;3)章节逻辑是否自洽、应用场景是否细化;4)文献综述是否构建"研究坐标系";5)实验/理论支撑是否充分。需特别注意三大雷区:科学错误(直接挂稿)、伪创新、场景不合理。写作时应强化选题依据的场景细化,通过"发展阶段+跨领域对比"提升文献综述质量。

Grow into a more wonderful you 1066

最小路径和——动态规划求解(Java实现)

最小路径和——动态规划求解(Java实现) 题目: 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。 示例: 输入: [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 7 解释: 因为路径 1→3→1→1→1 的总和最小。 这题和机器人走路的思路类似,就是状态方程有所不同,...

weixin_43395911的博客 896

JAVA mapper.map()_Java MapperFacade.map方法代码示例

import ma.glasnost.orika.MapperFacade; //导入方法依赖的package包/类@SuppressWarnings("unchecked")private To doMapping(final Class toClass, From from, String mappingName, MappingType mappingType, boolean beenM...

weixin_42387319的博客 2359

基于大模型与知识图谱的对话引导意图澄清系统技术解析

本文提出了一种基于知识图谱与大语言模型的意图澄清系统,旨在解决传统RAG模型在跨文档推理和主动澄清方面的不足。系统通过知识图谱驱动的多跳推理机制和链式思维引导的对话流程,实现了动态信息补全和意图澄清。核心创新包括:1)知识图谱构建与节点选择算法;2)标签探索策略与原子事实提取方法;3)主动澄清引擎的工作流程。实验表明,该系统能有效提升复杂查询的准确率,在医疗、金融等领域实现精准交互。关键代码展示了节点选择、标签匹配和事实提取等核心组件的实现细节。

417

Java 常见 bean mapper 的性能及原理分析

来源:albenw.github.io/posts/f6a7daea/背景在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优...

Java笔记虾 656

全功能爬虫框架:Botasaurus 的详细使用(现代化、反检测、高并发的智能爬虫框架)

Botasaurus是一个基于 Python 的全栈式智能数据采集框架。它深度融合了以下关键技术: 无头浏览器自动化(Headless Browser Automation):基于 Playwright 或 Puppeteer,支持 Chromium、Firefox、WebKit; 高级反检测引擎(Anti-Detection Engine):自动绕过 WebDriver、Canvas、AudioContext 等 50+ 种指纹检测; 声明式任务模型(Declarative Task Model):用装

数据知道的博客 4374

Debezium报错处理系列之第102篇:解决Flink CDC报错The db history topic or its content is fully or partially missing

Debezium报错处理系列之第101篇:解决Flink CDC报错The db history topic or its content is fully or partially missing

zhengzaifeidelushang的博客 2260

java signature 性能_Java常见bean mapper的性能及原理分析

背景在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文将讲述上面几个工具的使用、性能对比及原理分析。性能分析其实这几个工具要做的事情很简单,而且在使用上也是类似的,所以我觉得先给大家看看性能分析的对比结果,让大家有一个大概的认识。我是使...

weixin_39756895的博客 173

概率的接力:拆解大模型“预测下一个词“的底层逻辑

大模型的智能不在于它拥有了意识,而在于它用纯数学的架构,将人类语言的复杂结构压缩为可计算的向量轨迹与概率接力。当我们不再将其拟人化为"思考者",而是视作高维条件概率引擎时,才能真正驾驭它。预测下一个词,从来不是终点,而是起点。未来的核心竞争力,属于那些能洞察其统计本质、善用其模式生成能力,并以人类理性与事实为锚点的协作者。词元的轨迹终会止步于上下文窗口,但对技术底层的清醒认知,才是跨越幻觉、走向真实智能的基石。

AI浩 333

2024年最全Java常见bean mapper的性能及原理分析,linux多线程编程面试题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录需要这份系统化的资料的朋友,可以点击这里获取} else {if (!}if (![外链图片转存中…(img-yHMzfaj0-1715080522814)][外链图片转存中…(img-yARy9uMx-1715080522815)]

2401_84584552的博客 419

C#与倍福TwinCAT3通信实战:ADS协议封装与设备变量读写完整源码

倍福(Beckhoff)TwinCAT3 作为工业自动化领域主流的软PLC系统,其内置的ADS(Automation Device Specification)协议是实现上位机与TwinCAT3通信的核心标准。传统ADS通信开发存在门槛高(协议细节复杂)、稳定性差(缺乏统一异常处理)、复用性低(重复编码)等问题。本文基于 C# 技术栈,聚焦“协议封装标准化、变量读写易用化、通信稳定工业化”核心需求,提供一套完整的 ADS 协议封装方案与设备变量读写源码。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1987

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Mapper.map 对象转换工具类完整指南:从原理到实践

Mapper.map 对象转换工具类完整指南:从原理到实践

北执南念的博客 726

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Java Bean 映射框架性能对比:Dozer、Orika、MapStruct、ModelMapper、JMapper 选型实战指南

在 Spring Boot 多层架构项目中,持久化模型、领域模型与 DTO 之间的属性复制是高频且易出错的操作,手写 getter/setter 会迅速堆积大量样板代码。本文以 springboot-guide 仓库中的《5种常见Bean映射工具的性能比对》文档为主体,系统梳理 Dozer、Orika、MapStruct、ModelMapper、JMapper 五种主流 Bean 映射框架的引入方

gitblog_01114的博客 674

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

MapStruct:将数据从一个bean传输到另一个bean

将数据从一种形式转换为另一种形式在IT行业中是一种被高度利用的概念。 MapStruct通过在编译时生成映射器实现,允许基于注释的Bean转换。 这样可以确保在运行时没有性能开销。 什么是MapStruct? MapStruct是一个代码生成器,它基于约定优于配置的方法大大简化了Java Bean类型之间的映射的实现。 生成的映射代码使用简单的方法调用,因此速度快,类型安全且易于理解...

最佳 Java 编程 1688

解密 atlas-mapper 框架 1:框架概述与设计思路

本文介绍了Atlas Mapper框架的设计思路与实现原理,解决了传统Bean映射的痛点。通过编译时代码生成技术,Atlas Mapper在开发效率、运行性能和类型安全方面表现优异。开发者只需定义映射接口,框架会在编译时自动生成高性能的实现类,避免了手写代码的繁琐和反射调用的性能损耗。文中还展示了完整的映射示例,包括实体类定义、DTO转换和Spring Boot集成使用,体现了框架在简化开发流程、提升性能方面的优势。

MasterY的技术笔记 1120

Java进阶必读:反射与动态代理从原理到MyBatis、Spring AOP实战

在Java高级开发中,反射与动态代理是理解框架底层机制的两大基石。反射允许程序在运行时透视Class对象、调用私有构造器与方法,是Spring依赖注入、ORM框架映射等能力的技术前提;动态代理则通过运行时生成代理类,将方法调用统一收口到InvocationHandler或MethodInterceptor,从而在不侵入业务代码的前提下实现日志、事务、权限等横切逻辑。JDK动态代理基于接口生成代理对象,CGLIB通过继承子类突破接口限制,二者构成了Spring AOP和MyBatis Mapper的核心实现原

weixin_34375251的博客 363
上一篇: SpringBoot集成Swagger-Bootstrap-UI,页面更清爽!
下一篇: 骚操作 !IDEA 防止写代码沉迷插件 !
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值