• Java Bean拷贝工具Orika原理解析


      最近面试被问及对象拷贝怎样才能高效,实际上问的就是Orika或者BeanCopier的原理。由于网上对Orika原理的解析并不太多~因此本文重点讲解一下Orika的原理。(Orika是基于JavaBean规范的属性拷贝框架,所以不了解什么是JavaBean的话请先百度)

      首先,先纠正一下一些网上的错误说法,Java反射慢,所以要使用Orika基于Javasisst效率更好,我要说明的是Orika的整个流程是需要使用到Java的反射的,只是在真正拷贝的属性的时候没有使用反射。这里先简单讲解一下Orika的执行流程:先通过内省(反射)把JavaBean的属性(getset方法等)解析出来,进而匹配目标和源的属性-->接着根据这些属性和目标/源的匹配情况基于javasisst生成一个 GeneratedMapper的代理对象(真正的执行复制的对象)并放到缓存中-->接着就基于这个对象的 mapAtoB和mapBtoA方法对属性进行复制。

      有了上面的流程其实只需要看看GeneratedMapper这个代理对象到底是怎样的估计大家就明白了,直接贴出来。虚线上面就是复制的Orika需要复制的属性值,分别为id和productName,虚线下面就是GeneratedMapper的代理实现,可以看到实际上就是生成了一个get set的拷贝方法。看到这里大家就知道了实际上复制就是调用了GeneratedMapper.mapAtoB

    
    
    CanalSyncMapper canalSyncMapper = new CanalSyncMapper();
    canalSyncMapper.setId(1L);
    canalSyncMapper.setProductName("你好再见");
    TestProduct map = new DefaultMapperFactory.Builder().build().getMapperFacade().map(canalSyncMapper, TestProduct.class);
    -------------------------------------GeneratedMapper------------------------------------------------------------------------------------

    public
    void mapAtoB(java.lang.Object a, java.lang.Object b, ma.glasnost.orika.MappingContext mappingContext) { super.mapAtoB(a, b, mappingContext); // sourceType: CanalSyncMapper cn.danvid.canal_test.mapper.CanalSyncMapper source = ((cn.danvid.canal_test.mapper.CanalSyncMapper)a); // destinationType: TestProduct cn.danvid.canal_test.mapper.TestProduct destination = ((cn.danvid.canal_test.mapper.TestProduct)b); destination.setId(((java.lang.Long)source.getId())); destination.setProductName(((java.lang.String)source.getProductName()));#如果有多个字段需要复制就多个getset 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: TestProduct cn.danvid.canal_test.mapper.TestProduct source = ((cn.danvid.canal_test.mapper.TestProduct)a); // destinationType: CanalSyncMapper cn.danvid.canal_test.mapper.CanalSyncMapper destination = ((cn.danvid.canal_test.mapper.CanalSyncMapper)b); destination.setId(((java.lang.Long)source.getId())); destination.setProductName(((java.lang.String)source.getProductName()));#如果有多个字段需要复制就多个getset if(customMapper != null) { customMapper.mapBtoA(source, destination, mappingContext); } }

    可以看到实际上Orika使用map的时候,目标对象我们传的是class类,但到了GeneratedMapper.mapAtoB传入的是一个对象那么实际上目标对象是先基于Java反射创建出来,然后再复制的,所以Orika也要用到反射。
        其实到这里大概原理都讲完了,如果大家有兴趣也可以接着看我把细节再讲一下。

    ==========================================================================================================================

        大家点击MappFacade.map里面实际上源码是这样

     public <S, D> D map(final S sourceObject, final Class<D> destinationClass, final MappingContext context) {
            
            MappingStrategy strategy = null;
            try {
                if (destinationClass == null) {
                    throw new MappingException("'destinationClass' is required");
                }
                if (sourceObject == null) {
                    return null;
                }
                
                D result = context.getMappedObject(sourceObject, TypeFactory.valueOf(destinationClass));
                if (result == null) {
                    strategy = resolveMappingStrategy(sourceObject, null, destinationClass, false, context);#1.实际上就是创建GeneratedMapper
                    result = (D) strategy.map(sourceObject, null, context);                               #2.实际上调用的是这个方法
                }
                return result;
                
            } catch (MappingException e) {
                throw exceptionUtil.decorate(e);
            } catch (RuntimeException e) {
                if (!ExceptionUtility.originatedByOrika(e)) {
                    throw e;
                }
                MappingException me = exceptionUtil.newMappingException(e);
                me.setSourceClass(sourceObject.getClass());
                me.setDestinationType(TypeFactory.valueOf(destinationClass));
                me.setMappingStrategy(strategy);
                throw me;
            }
        }

    上面代码备注上有亮点,先讲第二点=>打断点会发现,在执行strategy.map的时候最终就是调用了GeneratedMapper.mapAtoB方法。

    讲完第二点实际上我们就是关注这个 GeneratedMapper的生成过程,也就是备注的第一点 strategy = resolveMappingStrategy(sourceObject, null, destinationClass, false, context);这个方法,从下面代码可以看出来strategy会被缓存起来。

      public <S, D> MappingStrategy resolveMappingStrategy(final S sourceObject, final java.lang.reflect.Type initialSourceType,
                final java.lang.reflect.Type initialDestinationType, final boolean mapInPlace, final MappingContext context) {
            
            Key key = new Key(getClass(sourceObject), initialSourceType, initialDestinationType, mapInPlace);
            MappingStrategy strategy = strategyCache.get(key);
            
            if (strategy == null) {
                
              ......
            }
            
            /*
             * Set the resolved types on the current mapping context; this can be
             * used by downstream Mappers to determine the originally resolved types
             */
            context.setResolvedSourceType(strategy.getAType());
            context.setResolvedDestinationType(strategy.getBType());
            context.setResolvedStrategy(strategy);
            
            return strategy;
        }

    接着我们就看看if (strategy == null)里面干了什么,如下,我们重点看备注部分

       
                @SuppressWarnings("unchecked")
                Type<S> sourceType = (Type<S>) (initialSourceType != null ? TypeFactory.valueOf(initialSourceType)
                        : typeOf(sourceObject));
                Type<D> destinationType = TypeFactory.valueOf(initialDestinationType);
                
                MappingStrategyRecorder strategyRecorder = new MappingStrategyRecorder(key, unenhanceStrategy);
                
                final Type<S> resolvedSourceType = normalizeSourceType(sourceObject, sourceType, destinationType);
                
                strategyRecorder.setResolvedSourceType(resolvedSourceType);
                strategyRecorder.setResolvedDestinationType(destinationType);
                
                if (!mapInPlace && canCopyByReference(destinationType, resolvedSourceType)) {
                    /*
                     * We can copy by reference when destination is assignable from
                     * source and the source is immutable
                     */
                    strategyRecorder.setCopyByReference(true);
                } else if (!mapInPlace && canConvert(resolvedSourceType, destinationType)) {
                    strategyRecorder.setResolvedConverter(mapperFactory.getConverterFactory().getConverter(resolvedSourceType, destinationType));
                    
                } else {
                    strategyRecorder.setInstantiate(true);
                    Type<? extends D> resolvedDestinationType = resolveDestinationType(context, sourceType, destinationType,
                            resolvedSourceType);
                    
                    strategyRecorder.setResolvedDestinationType(resolvedDestinationType);
                    strategyRecorder.setResolvedMapper(resolveMapper(resolvedSourceType, resolvedDestinationType, context));#重点关注
    if (!mapInPlace) { strategyRecorder.setResolvedObjectFactory( mapperFactory.lookupObjectFactory(resolvedDestinationType, resolvedSourceType, context)); } } strategy = strategyRecorder.playback(); if (log.isDebugEnabled()) { log.debug(strategyRecorder.describeDetails()); } MappingStrategy existing = strategyCache.putIfAbsent(key, strategy); if (existing != null) { strategy = existing; }
    重点关注resolveMapper(resolvedSourceType, resolvedDestinationType, context)这个方法,里面实际调用了mapperFactory.lookupMapper(mapperKey, context)方法,如下
      public Mapper<Object, Object> lookupMapper(MapperKey mapperKey, MappingContext context) {
         
            Mapper<?, ?> mapper = getRegisteredMapper(mapperKey.getAType(), mapperKey.getBType(), false);
            if (internalMapperMustBeGenerated(mapper, mapperKey)) {
                mapper = null;
            }
            if (mapper == null && useAutoMapping) {
                synchronized (this) {
                    mapper = getRegisteredMapper(mapperKey.getAType(), mapperKey.getBType(), false);
                    boolean internalMapperMustBeGenerated = internalMapperMustBeGenerated(mapper, mapperKey);
                    if (internalMapperMustBeGenerated) {
                        mapper = null;
                    }
                    if (mapper == null) {
                        try {
                    
                            ...        
                            ClassMapBuilder<?, ?> builder = classMap(mapperKey.getAType(), mapperKey.getBType()).byDefault(); #1.基于内省收集属性
                            for (MapperKey key : discoverUsedMappers(builder)) {
                                builder.use(key.getAType(), key.getBType());
                            }
                            final ClassMap<?, ?> classMap = builder.toClassMap();
                            
                            buildObjectFactories(classMap, context);
                            mapper = buildMapper(classMap, true, context);                #2.根据收集的属性创建GeneratedMapper
                            initializeUsedMappers(mapper, classMap, context);
                           ...
                        } catch (MappingException e) {
                            e.setSourceType(mapperKey.getAType());
                            e.setDestinationType(mapperKey.getBType());
                            throw exceptionUtil.decorate(e);
                        }
                    }
                }
                
            }
            return (Mapper<Object, Object>) mapper;
        }
    #1.基于内省收集属性:ClassMapBuilder<?, ?> builder = classMap(mapperKey.getAType(), mapperKey.getBType()).byDefault();这个方法里面最终会调用到下面这个
    protected ClassMapBuilder(Type<A> aType, Type<B> bType, MapperFactory mapperFactory, PropertyResolverStrategy propertyResolver,
                DefaultFieldMapper... defaults) {
            
            if (aType == null) {
                throw new MappingException("[aType] is required");
            }
            
            if (bType == null) {
                throw new MappingException("[bType] is required");
            }
            
            this.mapperFactory = mapperFactory;
            this.propertyResolver = propertyResolver;
            this.defaults = defaults;
            
            aProperties = propertyResolver.getProperties(aType);#这里就是使用了内省机制获取属性
            bProperties = propertyResolver.getProperties(bType);#这里就是使用了内省机制获取属性
    propertiesCacheA = new LinkedHashSet<String>(); propertiesCacheB = new LinkedHashSet<String>(); this.aType = aType; this.bType = bType; this.fieldsMapping = new LinkedHashSet<FieldMap>(); this.usedMappers = new LinkedHashSet<MapperKey>(); }

    最终会调用IntrospectorPropertyResolver.collectProperties 下面就是内省收集属性了。

    protected void collectProperties(Class<?> type, Type<?> referenceType, Map<String, Property> properties) {
            
            try {
                BeanInfo beanInfo = Introspector.getBeanInfo(type);
                PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
                
                for (final PropertyDescriptor pd : descriptors) {
                    
                    try {
    
                        Method readMethod = getReadMethod(pd, type);
                        if (!includeTransientFields && isTransient(readMethod)) {
                            continue;
                        }
                        Method writeMethod = getWriteMethod(pd, type, null);
                        
                        Property property = 
                                processProperty(pd.getName(), pd.getPropertyType(), readMethod, writeMethod, type, referenceType, properties);
                        
                        postProcessProperty(property, pd, readMethod, writeMethod, type, referenceType, properties);
                        
                    } catch (final Exception e) {
                        /*
                         * Wrap with info for the property we were trying to
                         * introspect
                         */
                        throw new RuntimeException("Unexpected error while trying to resolve property " + referenceType.getCanonicalName()
                                + ", [" + pd.getName() + "]", e);
                    }
                }
            } catch (IntrospectionException e) {
                throw new MappingException(e);
            }
        }
    #2.根据收集的属性创建GeneratedMapper:有了属性就可以基于Javasisst创建GeneratedMapper了,如下:

     可以看到mapperCode已经拼接生成了一个class的字符串(sourceBuilder),这个字符串如下

    package ma.glasnost.orika.generated;
    
    public class Orika_TestProduct_CanalSyncMapper_Mapper30589730646840$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: CanalSyncMapper
          cn.danvid.canal_test.mapper.CanalSyncMapper source = ((cn.danvid.canal_test.mapper.CanalSyncMapper)a); 
          // destinationType: TestProduct
          cn.danvid.canal_test.mapper.TestProduct destination = ((cn.danvid.canal_test.mapper.TestProduct)b); 
          destination.setId(((java.lang.Long)source.getId())); 
          destination.setProductName(((java.lang.String)source.getProductName())); 
          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: TestProduct
        cn.danvid.canal_test.mapper.TestProduct source = ((cn.danvid.canal_test.mapper.TestProduct)a); 
        // destinationType: CanalSyncMapper
        cn.danvid.canal_test.mapper.CanalSyncMapper destination = ((cn.danvid.canal_test.mapper.CanalSyncMapper)b); 
        destination.setId(((java.lang.Long)source.getId())); 
        destination.setProductName(((java.lang.String)source.getProductName())); 
            if(customMapper != null) { 
                 customMapper.mapBtoA(source, destination, mappingContext);
            }
        }
    }
    至此,应该明白的整个流程了把~
  • 相关阅读:
    PAIP.img ROM文件提取APK
    paip.提升程序稳定性最佳实践
    paip.验证码识别序列号的反转
    paip.android APK安装方法大总结系统应用的安装
    paip.php调试脱离IDE VC59
    paip.声音按键音延迟的解决
    paip.提升效率几款任务栏软件vc59
    paip.android 读取docx总结
    paip.C#.NET多线程访问 toolStripStatusLabel VC421
    paip.Image对象出现“对象当前正在其他地方使用或者GDI+中发生一般性错误的解决
  • 原文地址:https://www.cnblogs.com/danvid/p/12745997.html
Copyright © 2020-2023  润新知