通过Java7方法句柄实现高性能的BeanCopyUtil

需求场景

开发中我们经常需要将一个实体类的属性值赋值给另一个实体类,而两个实体类中的属性却大多数相同,如果实体类中的属性较多,手动一一赋值的方式就显得比较繁琐,并且很容易错误。
为了避免手动赋值的繁琐和易错问题,所以通常会使用工具类来实现两实体类属性值的copy;

常见的Bean copy util

有些开源的工具包已经为我们提供了这样工具类,例如

Apache commons-beanutils提供

commons-beanutils工具包下的两个工具类:

org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);

其中PropertyUtils性能稍微比BeanUtils高一点,但是这两个工具类性能都不是很高。不推荐使用。

Spring提供BeanUtils

而Spring中也给我们提供了一个BeanUtils可以实现实体类属性拷贝,其性能要比commons-beanutils中的高出很多,
其主要是利用反射+缓存的方式实现,通过缓存PropertyDescriptor,便于快速查询到Getter/Setter对应的Method.
避免每次反射获取Method,提高性能.(PropertyDescriptor中保存对应的Getter/Setter的Method)

org.springframework.beans.BeanUtils.copyProperties(source, target)
Cglib提供BeanCopier

Spring中还整合Cglib工具包,通过Cglib的BeanCopier也可实现高性能的实体类拷贝.
其利用动态生成Class的方式,拷贝性能比Spring的BeanUtils.copyProperties又高出很多.

 import org.springframework.cglib.beans.BeanCopier;
 BeanCopier copier = BeanCopier.create(source.getClass(), target.getClass(), false);
 copier.copy(source, target, null);

通过Java7的方法句柄实现高性能的BeanCopyUtil

本文将讲解如果通过Java7的方法句柄实现比Spring的BeanUtils.copyProperties更高性能的BeanCopyUtil,
甚至在一定情况下可以比Cglib的BeanCopier性能还要高. 文末将会给出Spring BeanUtils.copyProperties,
Cglib BeanCopier,以及自己实现的BeanCopyUtil,三个工具之间的性能对比。

方法句柄是Java7中所提供的API,目的是为了提供比反射具有更高性能的Java方法间接访问技术。
方法句柄包含一对名为invokeExact和invoke的特殊调用程序方法。两个调用程序方法都提供了对方法句柄的底层方法、构造函数、字段或其他操作的直接访问方式。

常见API

//获取直接访问字段值的方法句柄,注意不是实体类中的Getter方法
java.lang.invoke.MethodHandles.Lookup#findGetter
//获取直接给字段赋值的方法句柄,注意不是实体类中的Setter方法
java.lang.invoke.MethodHandles.Lookup#findSetter
//获取普通方法的方法句柄
java.lang.invoke.MethodHandles.Lookup#findVirtual
//获取静态方法的方法句柄
java.lang.invoke.MethodHandles.Lookup#findStatic
//获取静态字段的方法句柄
java.lang.invoke.MethodHandles.Lookup#findStaticGetter
java.lang.invoke.MethodHandles.Lookup#findStaticSetter

BeanCopyUtil的具体实现:注意代码中的注释

public class BeanCopyUtil{
    //用于缓存方法句柄,避免每次执行复制时获取,提高copy性能
    private static final ConcurrentHashMap<CacheKey, List<CopyMethodHandle>> methodHandleCache = new ConcurrentHashMap<>();

    //从缓存中获取或生成Getter/Setter方法句柄,并执行赋值
    public static <T> T copy(Object source, T target) {
        List<CopyMethodHandle> getSetMethodHandles = methodHandleCache
                .computeIfAbsent(new CacheKey(source.getClass(), target.getClass()),
                        key -> getCopyMethodHandles(source, target));
        try {
            for (CopyMethodHandle getSetMethodHandle : getSetMethodHandles) {
                getSetMethodHandle.invoke(source, target);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return target;
    }
    //获取Getter/Setter方法句柄
    private static <T> List<CopyMethodHandle> getCopyMethodHandles(Object source, T target) {
        ArrayList<CopyMethodHandle> methodHandles = new ArrayList<>();
        PropertyDescriptor[] sourcePropertyDescs = getPropertyDescs(source.getClass());
        Map<String, PropertyDescriptor> targetPropertyDescsMap = getPropertyDescsMap(target.getClass());

        for (PropertyDescriptor sourcePropertyDesc : sourcePropertyDescs) {
            Method readMethod = sourcePropertyDesc.getReadMethod();
            if (readMethod != null) {
                PropertyDescriptor targetPropertyDesc = targetPropertyDescsMap.get(sourcePropertyDesc.getName());
                Method writeMethod = null;
                if (targetPropertyDesc != null
                        && (writeMethod = targetPropertyDesc.getWriteMethod()) != null
                        && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    try {
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                            writeMethod.setAccessible(true);
                        }
                        //利用Java7的API获取方法句柄
                        MethodHandle getMethodHandle = MethodHandles.lookup().unreflect(readMethod);
                        MethodHandle setMethodHandle = MethodHandles.lookup().unreflect(writeMethod);

                        methodHandles.add(new CopyMethodHandle(getMethodHandle, setMethodHandle));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return methodHandles;
    }

    //缓存Key,需要重写hashCode和equals方法
    public static class CacheKey {
        private Class<?> source;
        private Class<?> target;

        public CacheKey(Class<?> source, Class<?> target) {
            this.source = source;
            this.target = target;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CacheKey cacheKey = (CacheKey) o;
            return Objects.equals(source, cacheKey.source) && Objects.equals(target, cacheKey.target);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(new Object[]{source, target});
        }
    }

    //封装源实体类的Getter方法句柄和目标实体类的Setter方法句柄
    static class CopyMethodHandle {
        private MethodHandle sourceGet;
        private MethodHandle targetSet;

        public CopyMethodHandle(MethodHandle sourceGet, MethodHandle targetSet) {
            this.sourceGet = sourceGet;
            this.targetSet = targetSet;
        }
        //执行方法句柄,调用Setter方法赋值
        public void invoke(Object source, Object target) throws Throwable {
            Object value = sourceGet.invoke(source);
            targetSet.invoke(target, value);
        }
    }

    private static PropertyDescriptor[] getPropertyDescs(Class<?> clazz) {
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(clazz);
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }
        if (beanInfo == null || beanInfo.getPropertyDescriptors() == null) {
            return new PropertyDescriptor[0];
        }
        return beanInfo.getPropertyDescriptors();
    }

    private static Map<String, PropertyDescriptor> getPropertyDescsMap(Class<?> clazz) {
        return Arrays.stream(getPropertyDescs(clazz))
                .collect(Collectors.toMap(PropertyDescriptor::getName, (desc) -> desc));
    }
}

压测
windows10系统,Java1.8.0U171版本,逻辑处理器8核,内存16G
开启15条线程分别调用对象拷贝方法,循环执行2次,预热1次,每次执行5s,结果输出平均耗时。

压测结果:
在这里插入图片描述
对BeanCopier缓存后的结果:
在这里插入图片描述
总结:
在没有对CglibCopier对象缓存之前,自己实现的BeanCopyUtil性能和Cglib的BeanCopier差不多,从数据上看略胜于BeanCopier。是Spring BeanUtils.copyProperties方法的两倍以上。

后对BeanCopier进行缓存,同样使用的CacheKey对象做为缓存Key,Cglib的BeanCopier略胜于BeanCopyUtil。Spring BeanUtils.copyProperties内存本身就存在缓存,但是仍然是最低的。

但是其实一般系统中Spring BeanUtils.copyProperties性能已经足够,
如果系统对赋值性能要求极高,可以考虑自定义的BeanCopyUtil和Cglib的BeanCopier。

对于Apache commons-beanutils中的BeanUtils.copyProperties不推荐使用。需要注意的是其方法参数含义和Spring的BeanUtils.copyProperties方法参数含义正好相反。


版权声明:本文为u013202238原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。