溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

BeanUtils、BeanCopier、Dozer、Orika 的功能和性能對(duì)比

發(fā)布時(shí)間:2021-06-16 10:52:07 來(lái)源:億速云 閱讀:270 作者:chen 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“BeanUtils、BeanCopier、Dozer、Orika 的功能和性能對(duì)比”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“BeanUtils、BeanCopier、Dozer、Orika 的功能和性能對(duì)比”吧!

 背景

在分層的代碼架構(gòu)中,層與層之間的對(duì)象避免不了要做很多轉(zhuǎn)換、賦值等操作,這些操作重復(fù)且繁瑣,于是乎催生出很多工具來(lái)優(yōu)雅,高效地完成這個(gè)操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文將講述上面幾個(gè)工具的使用、性能對(duì)比及原理分析。

性能分析

其實(shí)這幾個(gè)工具要做的事情很簡(jiǎn)單,而且在使用上也是類(lèi)似的,所以我覺(jué)得先給大家看看性能分析的對(duì)比結(jié)果,讓大家有一個(gè)大概的認(rèn)識(shí)。我是使用JMH來(lái)做性能分析的,代碼如下:

要復(fù)制的對(duì)象比較簡(jiǎn)單,包含了一些基本類(lèi)型;有一次warmup,因?yàn)橐恍┕ぞ呤切枰邦A(yù)編譯”和做緩存的,這樣做對(duì)比才會(huì)比較客觀;分別復(fù)制1000、10000、100000個(gè)對(duì)象,這是比較常用數(shù)量級(jí)了吧。

@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下運(yùn)行后的結(jié)果如下:

BeanUtils、BeanCopier、Dozer、Orika 的功能和性能對(duì)比

Score表示的是平均運(yùn)行時(shí)間,單位是微秒。從執(zhí)行效率來(lái)看,可以看出 beanCopier > orika > springBeanUtil > dozer > apacheBeanUtil。這樣的結(jié)果跟它們各自的實(shí)現(xiàn)原理有很大的關(guān)系,

下面將詳細(xì)每個(gè)工具的使用及實(shí)現(xiàn)原理。

Spring的BeanUtils

使用

這個(gè)工具可能是大家日常使用最多的,因?yàn)槭荢pring自帶的,使用也簡(jiǎn)單:BeanUtils.copyProperties(sourceVO, targetVO);

原理

Spring BeanUtils的實(shí)現(xiàn)原理也比較簡(jiǎn)答,就是通過(guò)Java的Introspector獲取到兩個(gè)類(lèi)的PropertyDescriptor,對(duì)比兩個(gè)屬性具有相同的名字和類(lèi)型,如果是,則進(jìn)行賦值(通過(guò)ReadMethod獲取值,通過(guò)WriteMethod賦值),否則忽略。

為了提高性能Spring對(duì)BeanInfo和PropertyDescriptor進(jìn)行了緩存。

(源碼基于: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類(lèi)的屬性(有緩存)      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類(lèi)的屬性(有緩存)        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());        if (sourcePd != null) {         Method readMethod = sourcePd.getReadMethod();         if (readMethod != null &&                  //判斷target的setter方法入?yún)⒑蛃ource的getter方法返回類(lèi)型是否一致           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);          }         }        }       }      }     }

小結(jié)

Spring BeanUtils的實(shí)現(xiàn)就是這么簡(jiǎn)潔,這也是它性能比較高的原因。

不過(guò),過(guò)于簡(jiǎn)潔就失去了靈活性和可擴(kuò)展性了,Spring BeanUtils的使用限制也比較明顯,要求類(lèi)屬性的名字和類(lèi)型一致,這點(diǎn)在使用時(shí)要注意。

Apache的BeanUtils

使用

Apache的BeanUtils和Spring的BeanUtils的使用是一樣的:

BeanUtils.copyProperties(targetVO, sourceVO);

要注意,source和target的入?yún)⑽恢貌煌?/p>

原理

Apache的BeanUtils的實(shí)現(xiàn)原理跟Spring的BeanUtils一樣,也是主要通過(guò)Java的Introspector機(jī)制獲取到類(lèi)的屬性來(lái)進(jìn)行賦值操作,對(duì)BeanInfo和PropertyDescriptor同樣有緩存,但是Apache BeanUtils加了一些不那么使用的特性(包括支持Map類(lèi)型、支持自定義的DynaBean類(lèi)型、支持屬性名的表達(dá)式等等)在里面,使得性能相對(duì)Spring的BeanUtils來(lái)說(shuō)有所下降。

(源碼基于: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類(lèi)型            } 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());                   }                }            // 標(biāo)準(zhǔn)的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                    }                    //是否可讀和可寫(xiě)                   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                        }                    }                }            }        }

小結(jié)

Apache BeanUtils的實(shí)現(xiàn)跟Spring BeanUtils總體上類(lèi)似,但是性能卻低很多,這個(gè)可以從上面性能比較看出來(lái)。阿里的Java規(guī)范是不建議使用的。

另外,關(guān)注公眾號(hào)Java核心技術(shù),在后臺(tái)回復(fù):手冊(cè),可以獲取最新阿里的 Java 開(kāi)發(fā)手冊(cè)。

BeanCopier

使用

BeanCopier在cglib包里,它的使用也比較簡(jiǎn)單:

@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));    }

只需要預(yù)先定義好要轉(zhuǎn)換的source類(lèi)和target類(lèi)就好了,可以選擇是否使用Converter,這個(gè)下面會(huì)說(shuō)到。

在上面的性能測(cè)試中,BeanCopier是所有中表現(xiàn)最好的,那么我們分析一下它的實(shí)現(xiàn)原理。

原理

BeanCopier的實(shí)現(xiàn)原理跟BeanUtils截然不同,它不是利用反射對(duì)屬性進(jìn)行賦值,而是直接使用cglib來(lái)生成帶有的get/set方法的class類(lèi),然后執(zhí)行。由于是直接生成字節(jié)碼執(zhí)行,所以BeanCopier的性能接近手寫(xiě)

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創(chuàng)建一個(gè)BeanCopier出來(lái),然后調(diào)用create方法來(lái)生成字節(jié)碼。

KEY_FACTORY其實(shí)就是用cglib通過(guò)BeanCopierKey接口生成出來(lái)的一個(gè)類(lèi)

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

通過(guò)設(shè)置

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

可以讓cglib輸出生成類(lèi)的class文件,我們可以反編譯看看里面的代碼

下面是KEY_FACTORY的類(lèi)

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等方法。。。   }

繼續(xù)跟蹤Generator.create方法,由于Generator是繼承AbstractClassGenerator,這個(gè)AbstractClassGenerator是cglib用來(lái)生成字節(jié)碼的一個(gè)模板類(lèi),Generator的super.create其實(shí)調(diào)用AbstractClassGenerator的create方法,最終會(huì)調(diào)用到Generator的模板方法generateClass方法,我們不去細(xì)究AbstractClassGenerator的細(xì)節(jié),重點(diǎn)看generateClass。

這個(gè)是一個(gè)生成java類(lèi)的方法,理解起來(lái)就好像我們平時(shí)寫(xiě)代碼一樣。

public void generateClass(ClassVisitor v) {        Type sourceType = Type.getType(source);        Type targetType = Type.getType(target);        ClassEmitter ce = new ClassEmitter(v);        //開(kāi)始“寫(xiě)”類(lèi),這里有修飾符、類(lèi)名、父類(lèi)等信息        ce.begin_class(Constants.V1_2,                       Constants.ACC_PUBLIC,                       getClassName(),                       BEAN_COPIER,                       null,                       Constants.SOURCE_FILE);        //沒(méi)有構(gòu)造方法        EmitUtils.null_constructor(ce);       //開(kāi)始“寫(xiě)”一個(gè)方法,方法名是copy        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);        //通過(guò)Introspector獲取source類(lèi)和target類(lèi)的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);        }        //通過(guò)屬性名來(lái)生成轉(zhuǎn)換的代碼        //以setter作為遍歷        for (int i = 0; i < setters.length; i++) {          PropertyDescriptor setter = setters[i];            //根據(jù)setter的name獲取getter            PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());            if (getter != null) {                //獲取讀寫(xiě)方法                MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());                MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());                //如果用了useConverter,則進(jìn)行下面的拼裝代碼方式                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用來(lái)判斷getter和setter是否類(lèi)型一致                } 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());    }

即使沒(méi)有使用過(guò)cglib也能讀懂生成代碼的流程吧,我們看看沒(méi)有使用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());        }    }

在對(duì)比上面生成代碼的代碼是不是闊然開(kāi)朗了。

再看看使用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();        }    }

小結(jié)

BeanCopier性能確實(shí)很高,但從源碼可以看出BeanCopier只會(huì)拷貝名稱(chēng)和類(lèi)型都相同的屬性,而且如果一旦使用Converter,BeanCopier只使用Converter定義的規(guī)則去拷貝屬性,所以在convert方法中要考慮所有的屬性。

Dozer

使用

上面提到的BeanUtils和BeanCopier都是功能比較簡(jiǎn)單的,需要屬性名稱(chēng)一樣,甚至類(lèi)型也要一樣。但是在大多數(shù)情況下這個(gè)要求就相對(duì)苛刻了,要知道有些VO由于各種原因不能修改,有些是外部接口SDK的對(duì)象,

有些對(duì)象的命名規(guī)則不同,例如有駝峰型的,有下劃線的等等,各種什么情況都有。所以我們更加需要的是更加靈活豐富的功能,甚至可以做到定制化的轉(zhuǎn)換。

Dozer就提供了這些功能,有支持同名隱式映射,支持基本類(lèi)型互相轉(zhuǎn)換,支持顯示指定映射關(guān)系,支持exclude字段,支持遞歸匹配映射,支持深度匹配,支持Date to String的date-formate,支持自定義轉(zhuǎn)換Converter,支持一次mapping定義多處使用,支持EventListener事件監(jiān)聽(tīng)等等。不僅如此,Dozer在使用方式上,除了支持API,還支持XML和注解,滿(mǎn)足大家的喜好。更多的功能可以參考這里

由于其功能很豐富,不可能每個(gè)都演示,這里只是給個(gè)大概認(rèn)識(shí),更詳細(xì)的功能,或者XML和注解的配置,請(qǐng)看官方文檔。

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的實(shí)現(xiàn)原理本質(zhì)上還是用反射/Introspector那套,但是其豐富的功能,以及支持多種實(shí)現(xiàn)方式(API、XML、注解)使得代碼看上去有點(diǎn)復(fù)雜,在翻閱代碼時(shí),我們大可不必理會(huì)這些類(lèi),只需要知道它們大體的作用就行了,重點(diǎn)關(guān)注核心流程和代碼的實(shí)現(xiàn)。下面我們重點(diǎn)看看構(gòu)建mapper的build方法和實(shí)現(xiàn)映射的map方法。

build方法很簡(jiǎn)單,它是一個(gè)初始化的動(dòng)作,就是通過(guò)用戶(hù)的配置來(lái)構(gòu)建出一系列后面要用到的配置對(duì)象、上下文對(duì)象,或其他封裝對(duì)象,我們不必深究這些對(duì)象是怎么實(shí)現(xiàn)的,從名字上我們大概能猜出這些對(duì)象是干嘛,負(fù)責(zé)什么就可以了。

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方法是映射對(duì)象的過(guò)程,其入口是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 {            //構(gòu)建ClassMap            //ClassMap是包括src類(lèi)和dest類(lèi)和其他配置的一個(gè)封裝           classMap = getClassMap(srcObj.getClass(), destType, mapId);            //注冊(cè)事件            eventManager.on(new DefaultEvent(EventTypes.MAPPING_STARTED, classMap, null, srcObj, result, null));                 //看看有沒(méi)有自定義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;                }            }           //優(yōu)先使用自定義converter進(jìn)行映射            if (converterClass != null) {                return (T)mapUsingCustomConverter(converterClass, srcObj.getClass(), srcObj, destType, result, null, true);            }            //也是對(duì)配置進(jìn)行了封裝           BeanCreationDirective creationDirective =                   new BeanCreationDirective(srcObj, classMap.getSrcClassToMap(), classMap.getDestClassToMap(), destType,                                              classMap.getDestClassBeanFactory(), classMap.getDestClassBeanFactoryId(), classMap.getDestClassCreateMethod(),                                              classMap.getDestClass().isSkipConstructor());            //繼續(xù)進(jìn)行映射            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方法會(huì)一直調(diào)用到mapFromFieldMap方法,而在沒(méi)有自定義converter的情況下會(huì)調(diào)用mapOrRecurseObject方法。

2021 最新 Java 面試題出爐?。◣看鸢福?/p>

大多數(shù)情況下字段的映射會(huì)在這個(gè)方法做一般的解析

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;        }       //對(duì)Map類(lèi)型的處理        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;        }        //對(duì)基本類(lèi)型的映射處理        //PrimitiveOrWrapperConverter類(lèi)支持兼容了基本類(lèi)型之間的互相轉(zhuǎn)換        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);            }        }        //對(duì)集合類(lèi)型的映射處理        if (MappingUtils.isSupportedCollection(srcFieldClass) && (MappingUtils.isSupportedCollection(destFieldType))) {            return mapCollection(srcObj, srcFieldValue, fieldMap, destObj);        }        //對(duì)枚舉類(lèi)型的映射處理        if (MappingUtils.isEnumType(srcFieldClass, destFieldType)) {            return mapEnum((Enum)srcFieldValue, (Class<Enum>)destFieldType);        }        if (fieldMap.getDestDeepIndexHintContainer() != null) {            destFieldType = fieldMap.getDestDeepIndexHintContainer().getHint();        }        //其他復(fù)雜對(duì)象類(lèi)型的處理        return mapCustomObject(fieldMap, destObj, destFieldType, destFieldName, srcFieldValue);    }

mapCustomObject方法。其實(shí)你會(huì)發(fā)現(xiàn)這個(gè)方法最重要的一點(diǎn)就是做遞歸處理,無(wú)論是最后調(diào)用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;    }

小結(jié)

Dozer功能強(qiáng)大,但底層還是用反射那套,所以在性能測(cè)試中它的表現(xiàn)一般,僅次于Apache的BeanUtils。如果不追求性能的話,可以使用。

Orika

Orika可以說(shuō)是幾乎集成了上述幾個(gè)工具的優(yōu)點(diǎn),不僅具有豐富的功能,底層使用Javassist生成字節(jié)碼,運(yùn)行 效率很高的。

使用

Orika基本支持了Dozer支持的功能,這里我也是簡(jiǎn)單介紹一下Orika的使用,具體更詳細(xì)的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));    }

原理

在講解實(shí)現(xiàn)原理時(shí),我們先看看Orika在背后干了什么事情。

通過(guò)增加以下配置,我們可以看到Orika在做映射過(guò)程中生成mapper的源碼和字節(jié)碼。

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);      }     }    }

這個(gè)mapper類(lèi)就兩個(gè)方法mapAtoB和mapBtoA,從名字看猜到前者是負(fù)責(zé)src -> dest的映射,后者是負(fù)責(zé)dest -> src的映射。

好,我們們看看實(shí)現(xiàn)的過(guò)程。

Orika的使用跟Dozer的類(lèi)似,首先通過(guò)配置生成一個(gè)MapperFactory,再用MapperFacade來(lái)作為映射的統(tǒng)一入口,這里MapperFactory和MapperFacade都是單例的。mapperFactory在做配置類(lèi)映射時(shí),只是注冊(cè)了ClassMap,還沒(méi)有真正的生成mapper的字節(jié)碼,是在第一次調(diào)用getMapperFacade方法時(shí)才初始化mapper。下面看看getMapperFacade。

(源碼基于 ma.glasnost.orika:orika-core:1.5.4)

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

利用注冊(cè)的ClassMap信息和MappingContext上下文信息來(lái)構(gòu)造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>();                //重點(diǎn)看這里                //在使用mapperFactory配置classMap時(shí),會(huì)存放在classMapRegistry里                for (ClassMap<?, ?> classMap : classMapRegistry.values()) {                    //對(duì)每個(gè)classMap生成一個(gè)mapper,重點(diǎn)看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());      //調(diào)用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 + ")");       }           //構(gòu)建用來(lái)生成源碼及字節(jié)碼的上下文        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方法基本就是手寫(xiě)代碼的過(guò)程,有興趣的讀者可以看看        mappedFields.addAll(addMapMethod(mapperCode, false, classMap, logDetails));             //生成一個(gè)mapper實(shí)例        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;        }            classMapclassMap = 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實(shí)例

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

這里的compilerStrategy的默認(rèn)是用Javassist(你也可以自定義生成字節(jié)碼的策略)

JavassistCompilerStrategy的compileClass方法

這基本上就是一個(gè)使用Javassist的過(guò)程,經(jīng)過(guò)前面的各種鋪墊(通過(guò)配置信息、上下文信息、拼裝java源代碼等等),終于來(lái)到這一步

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 {                //創(chuàng)建一個(gè)類(lèi)                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 {            //把源碼寫(xiě)到磁盤(pán)(通過(guò)上面提到的配置)            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方法           //直接用源碼通過(guò)Javassist往類(lèi)“加”方法            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;                }                     }            //生成類(lèi)            compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());                  //字節(jié)碼文件寫(xiě)磁盤(pán)           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類(lèi)生成了,現(xiàn)在就看在調(diào)用MapperFacade的map方法是如何使用這個(gè)mapper類(lèi)的。

其實(shí)很簡(jiǎn)單,還記得生成的mapper是放到mappersRegistry嗎,跟蹤代碼,在resolveMappingStrategy方法根據(jù)typeA和typeB在mappersRegistry找到mapper,在調(diào)用mapper的mapAtoB或mapBtoA方法即可。

小結(jié)

總體來(lái)說(shuō),Orika是一個(gè)功能強(qiáng)大的而且性能很高的工具,推薦使用。

總結(jié)

通過(guò)對(duì)BeanUtils、BeanCopier、Dozer、Orika這幾個(gè)工具的對(duì)比,我們得知了它們的性能以及實(shí)現(xiàn)原理。在使用時(shí),我們可以根據(jù)自己的實(shí)際情況選擇,推薦使用Orika。

到此,相信大家對(duì)“BeanUtils、BeanCopier、Dozer、Orika 的功能和性能對(duì)比”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI