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

本文对比分析了Spring BeanUtils、Apache BeanUtils和BeanCopier在Java bean映射中的性能和实现原理。Spring BeanUtils简洁但缺乏灵活性,Apache BeanUtils因额外功能导致性能下降,而BeanCopier借助CGLIB实现高效映射。文中还探讨了各库的使用场景和限制。
摘要由CSDN通过智能技术生成

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)

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值