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

throw new IllegalArgumentException(“Target class [” + target.getClass().getName() +

“] not assignable to Editable class [” + editable.getName() + “]”);

}

actualEditable = editable;

}

//获取target类的属性(有缓存)

PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);

List 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 B e a n C o p i e r K e y BeanCopierKey BeanCopierKey K e y F a c t o r y B y C G L I B KeyFactoryByCGLIB KeyFactoryByCGLIB$f32401fd extends KeyFactory implements BeanCopierKey {

private final String FIELD_0;

private final String FIELD_1;

private final boolean FIELD_2;

public BeanCopier B e a n C o p i e r K e y BeanCopierKey BeanCopierKey K e y F a c t o r y B y C G L I B KeyFactoryByCGLIB KeyFactoryByCGLIB$f32401fd() {

}

public Object newInstance(String var1, String var2, boolean var3) {

return new BeanCopier B e a n C o p i e r K e y BeanCopierKey BeanCopierKey K e y F a c t o r y B y C G L I B KeyFactoryByCGLIB KeyFactoryByCGLIB$f32401fd(var1, var2, var3);

}

public BeanCopier B e a n C o p i e r K e y BeanCopierKey BeanCopierKey K e y F a c t o r y B y C G L I B KeyFactoryByCGLIB 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 B e a n C o p i e r B y C G L I B BeanCopierByCGLIB BeanCopierByCGLIBd1d970c8 extends BeanCopier {

public Object B e a n C o p i e r B y C G L I B BeanCopierByCGLIB BeanCopierByCGLIBd1d970c8() {

}

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 B e a n C o p i e r B y C G L I B BeanCopierByCGLIB BeanCopierByCGLIBd1d970c7 extends BeanCopier {

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EDate;

private static final Class CGLIB l o a d c l a s s load_class loadclassbeanmapper_compare$2Evo$2ESourceVO$24Inner;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EList;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EMap;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EInteger;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2ELong;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EByte;

private static final Class CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EString;

public Object B e a n C o p i e r B y C G L I B BeanCopierByCGLIB BeanCopierByCGLIBd1d970c7() {

}

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 l o a d c l a s s load_class loadclassjava$2Eutil$2EDate, “setDate1”));

var4.setIn((Inner)var3.convert(var5.getIn(), CGLIB l o a d c l a s s load_class loadclassbeanmapper_compare$2Evo$2ESourceVO$24Inner, “setIn”));

var4.setListData((List)var3.convert(var5.getListData(), CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EList, “setListData”));

var4.setMapData((Map)var3.convert(var5.getMapData(), CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EMap, “setMapData”));

var4.setP1((Integer)var3.convert(var5.getP1(), CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EInteger, “setP1”));

var4.setP2((Long)var3.convert(var5.getP2(), CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2ELong, “setP2”));

var4.setP3((Byte)var3.convert(var5.getP3(), CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EByte, “setP3”));

var4.setPattr1((String)var3.convert(var5.getPattr1(), CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EString, “setPattr1”));

var4.setSeq((Long)var3.convert(var5.getSeq(), CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2ELong, “setSeq”));

}

static void CGLIB$STATICHOOK1() {

CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EDate = Class.forName(“java.util.Date”);

CGLIB l o a d c l a s s load_class loadclassbeanmapper_compare$2Evo$2ESourceVO 24 I n n e r   =   C l a s s . f o r N a m e ( " b e a n m a p p e r c o m p a r e . v o . S o u r c e V O 24Inner = Class.forName("beanmapper_compare.vo.SourceVO 24Inner = Class.forName("beanmappercompare.vo.SourceVOInner");

CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EList = Class.forName(“java.util.List”);

CGLIB l o a d c l a s s load_class loadclassjava$2Eutil$2EMap = Class.forName(“java.util.Map”);

CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EInteger = Class.forName(“java.lang.Integer”);

CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2ELong = Class.forName(“java.lang.Long”);

CGLIB l o a d c l a s s load_class loadclassjava$2Elang$2EByte = Class.forName(“java.lang.Byte”);

CGLIB l o a d c l a s s load_class loadclassjava$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 mappingFiles,

BeanContainer beanContainer,

DestBeanCreator destBeanCreator,

DestBeanBuilderCreator destBeanBuilderCreator,

BeanMappingGenerator beanMappingGenerator,

PropertyDescriptorFactory propertyDescriptorFactory,

List customConverters,

List mappingsFileData,

List 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 mapGeneral(Object srcObj, final Class destClass, final T destObj, final String mapId) {

srcObj = MappingUtils.deProxy(srcObj, beanContainer);

Class destType;

T result;

if (destClass == null) {

destType = (Class)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)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_KaTeX parse error: Expected group after '_' at position 76: …ator(); 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_KaTeX parse error: Expected group after '_' at position 76: …ator(); 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来作为映射的统一入口,这里MapperFactoryMapperFacade都是单例的。mapperFactory在做配置类映射时,只是注册了ClassMap,还没有真正的生成mapper的字节码,是在第一次调用getMapperFacade方法时才初始化mapper。下面看看getMapperFacade。

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

public MapperFacade getMapperFacade() {

if (!isBuilt) {

synchronized (mapperFacade) {

if (!isBuilt) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

读者福利

分享一份自己整理好的Java面试手册,还有一些面试题pdf

不要停下自己学习的脚步

字节跳动的面试分享,为了拿下这个offer鬼知道我经历了什么

字节跳动的面试分享,为了拿下这个offer鬼知道我经历了什么

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
Seq()) == 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来作为映射的统一入口,这里MapperFactoryMapperFacade都是单例的。mapperFactory在做配置类映射时,只是注册了ClassMap,还没有真正的生成mapper的字节码,是在第一次调用getMapperFacade方法时才初始化mapper。下面看看getMapperFacade。

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

public MapperFacade getMapperFacade() {

if (!isBuilt) {

synchronized (mapperFacade) {

if (!isBuilt) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-epGZ5hbX-1713143156816)]

[外链图片转存中…(img-Sw0pckkf-1713143156817)]

[外链图片转存中…(img-H04t6nA8-1713143156817)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

读者福利

分享一份自己整理好的Java面试手册,还有一些面试题pdf

不要停下自己学习的脚步

[外链图片转存中…(img-bEDLyjck-1713143156817)]

[外链图片转存中…(img-gyuRD2Zr-1713143156817)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值