最近面試被問及對象拷貝怎樣才能高效,實際上問的就是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); } }
}
至此,應該明白的整個流程了把~