Bean属性Copy问题整理汇总

Bean属性Copy问题整理汇总

1. bean copy性能对比
  • 结论: set > cglib > spring > apache

常用bean copy方法:

  • org.apache.commons.beanutils.BeanUtils.copyProperties(target, source)
    • apache的BeanUtils日志级别是debug的,会打印一堆日志,logback关闭日志方法 <logger name="org.apache.commons.beanutils.BeanUtils" level="ERROR" additivity="false" />
  • org.springframework.beans.BeanUtils.copyProperties(source,target)
  • BeanCopier beanCopier=org.springframework.cglib.beans.BeanCopier.create(source,target,boolean useConverter); beanCopier.copy(source, target, converter)
    • 实质是动态生成一个子类,子类中包含一个方法:public void copy(Object var1, Object var2, Converter var3){}其中var1就是source,var2就是target,并且copy()方法中是直接调用的set方法,因此性能和直接java set基本一致
  • java set方法
工具类数量耗时(ms)
apache1w1070
spring1w198
cglib 循环内创建BeanCopie1w20
cglib 循环外创建BeanCopie1w3
java set1w0
apache10w10491
spring10w1994
cglib 循环内创建BeanCopie10w198
cglib 循环外创建BeanCopie10w13
java set10w8
  • 特别 org.apache.commons.beanutils.BeanUtils.copyProperties随着数量的增加性能急剧下降(特别是并发时候一定不能使用)
  • 相对来说cglib的性能会好上很多,但是 org.springframework.cglib.beans.BeanCopier.create()创建也非常耗时,使用时候可以考虑根据做内存缓存或者在循环外面执行,性能会好上很多
  • 在性能要很高情况下可以使用cglib、或者直接set方法
 public static BeanDemo createBeanDemo(int pk) {
        return new BeanDemo(pk, "a_" + pk, "b_" + pk, "c_" + pk, "d_" + pk, "e_" + pk, "f_" + pk, "g_" + pk, "h_" + pk, "j_" + pk, "k_" + pk);
    }

    public static void beanCopyCompare( int loopNum) {

        try {
//            int loopNum = 50000;
            List<BeanDemo> dataList = new ArrayList<>();
            for (int i = 0; i < loopNum; i++) {
                dataList.add(createBeanDemo(i));
            }
            BeanDemo demo = new BeanDemo();
            StopWatch sw = new StopWatch("各个bean copy新能对比");
            sw.start("apache-BeanUtils");
            for (int i = 0; i < loopNum; i++) {
                org.apache.commons.beanutils.BeanUtils.copyProperties(demo, dataList.get(i));
            }
            sw.stop();
            sw.start("spring-BeanUtils");
            for (int i = 0; i < loopNum; i++) {
                org.springframework.beans.BeanUtils.copyProperties(dataList.get(i), demo);
            }
            sw.stop();

            sw.start("cglib-BeanUtils createBeanCopierInLoop");
            for (int i = 0; i < loopNum; i++) {
            org.springframework.cglib.beans.BeanCopier beanCopier2 = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
                beanCopier2.copy(dataList.get(i), demo, null);
            }
            sw.stop();

            org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
            sw.start("cglib-BeanUtils  createBeanCopierOutOfLoop");
            for (int i = 0; i < loopNum; i++) {
                beanCopier.copy(dataList.get(i), demo, null);
            }
            sw.stop();

            sw.start("set");
            for (int i = 0; i < loopNum; i++) {
                BeanDemo.directSet(dataList.get(i), demo);
            }
            sw.stop();
            System.out.println(sw.prettyPrint());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 本地执行效果
-- loopNum=1w
StopWatch '各个bean copy新能对比': running time (millis) = 1291
-----------------------------------------
ms     %     Task name
-----------------------------------------
01070  083%  apache-BeanUtils
00198  015%  spring-BeanUtils
00020  002%  cglib-BeanUtils createBeanCopierInLoop
00003  000%  cglib-BeanUtils  createBeanCopierOutOfLoop
00000  000%  set

loopNum=1w
StopWatch '各个bean copy新能对比': running time (millis) = 12704
-----------------------------------------
ms     %     Task name
-----------------------------------------
10491  083%  apache-BeanUtils
01994  016%  spring-BeanUtils
00198  002%  cglib-BeanUtils createBeanCopierInLoop
00013  000%  cglib-BeanUtils  createBeanCopierOutOfLoop
00008  000%  set

  • 1.org.apache.commons.beanutils.BeanUtils.copyProperties()源码:BeanUtilsBean.getInstance()中用使用到synchronized因此有并发要求时候性能会很差
    public static void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {

        BeanUtilsBean.getInstance().copyProperties(dest, orig);
    }

   public static BeanUtilsBean getInstance() {
        return BEANS_BY_CLASSLOADER.get();
    }

  public synchronized T get() {
        // synchronizing the whole method is a bit slower
        // but guarantees no subtle threading problems, and there's no
        // need to synchronize valueByClassLoader

        // make sure that the map is given a change to purge itself
        valueByClassLoader.isEmpty();
        try {

            final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            if (contextClassLoader != null) {

                T value = valueByClassLoader.get(contextClassLoader);
                if ((value == null)
                && !valueByClassLoader.containsKey(contextClassLoader)) {
                    value = initialValue();
                    valueByClassLoader.put(contextClassLoader, value);
                }
                return value;

            }

        } catch (final SecurityException e) { /* SWALLOW - should we log this? */ }

        // if none or exception, return the globalValue
        if (!globalValueInitialized) {
            globalValue = initialValue();
            globalValueInitialized = true;
        }//else already set
        return globalValue;
    }
  • 2.org.springframework.beans.BeanUtils.copyProperties源码:
  • 注意里面有调用Modifier.isPublic()方法,只能copy public修饰的属性
  • 性能差的原因在for循环
    public static void copyProperties(Object source, Object target) throws BeansException {
        copyProperties(source, target, null, (String[]) null);
    }

    private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
            throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
       //for循环导致性能下降
        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            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);
                            }
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }
  • 3.org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false); 生成子类源码:

    public static void main(String[] args) {
        //查生成的源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "d:/66");
        org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
        
        BeanDemo createBeanDemo =createBeanDemo(1);
        BeanDemo demo = new BeanDemo();
        beanCopier.copy(createBeanDemo, demo, null);
    }

public class Object$$BeanCopierByCGLIB$$59d4a388 extends BeanCopier {
    public Object$$BeanCopierByCGLIB$$59d4a388() {
    }

    public void copy(Object var1, Object var2, Converter var3) {
        BeanDemo var10000 = (BeanDemo)var2;
        BeanDemo var10001 = (BeanDemo)var1;
        var10000.setF1(((BeanDemo)var1).getF1());
        var10000.setF10(var10001.getF10());
        var10000.setF2(var10001.getF2());
        var10000.setF3(var10001.getF3());
        var10000.setF4(var10001.getF4());
        var10000.setF5(var10001.getF5());
        var10000.setF6(var10001.getF6());
        var10000.setF7(var10001.getF7());
        var10000.setF8(var10001.getF8());
        var10000.setF9(var10001.getF9());
        var10000.setPk(var10001.getPk());
    }
}
2. bean copy部分属性

应用场景:由于性能要求在内存缓存了新闻实体的全部属性信息,每次拿新闻实体信息时候先去内存缓存拿,拿不到在去db取(reids或者mysql等),由于业务逻辑原因需要只copy缓存bean中部分属性到目标bean,业务场景:

Bean b1=new Bean 
b1.setF1(123);

//从缓存获取bean
Bean orgBean=BeanCache.get(pk)

// 此处需要把orgBean中除了f1之外信息赋值给b1,因此不能使用全bean copy
//传统写法一堆setter()方法...

  • org.apache.commons.beanutils.BeanUtils.copyProperty(final Object bean, final String name, final Object value) 效率太慢
  • 核心代码解析通过jdk自带的Introspector(内省)类,Introspector.getBeanInfo(currentClass).getPropertyDescriptors()获取类中属性对应的getter、setter方法信息
    • Method writeMethod=propertyDescriptor.getWriteMethod()对应setter方法
    • Method readMethod=propertyDescriptor.getReadMethod()对应getter方法
  • 调用 writeMethod.invoke(Object obj, Object… args),对bean的属性进行赋值
PropertyDescriptor[] propertyDescriptorList = Introspector.getBeanInfo(currentClass).getPropertyDescriptors();
  • 完全证代码实现


/**
 * @Author lts
 * @Date 2021/1/18  19:56
 * @Description
 */
public class BeanUtilsMy {

    static final Map<String, Map<String, Method>> classWriteMethodCache = new HashMap<>();
    static final Map<String, Map<String, Method>> classReadMethodCache = new HashMap<>();

    /**
     * 方法描述: 设置obj中属性名为name的字段值为value
     *
     * @param obj
     * @param name
     * @param value
     * @return
     * @author: lts
     * @date: 2021-01-21
     */
    public static void copyProperty(Object obj, String name, Object value) {
        if (name == null || name.equals("")) {
            return;
        }
        String className = obj.getClass().getName();
        Map<String, Method> writeMethodCache;
        if (!classWriteMethodCache.containsKey(className)) {
            writeMethodCache = getWriteMethod(obj.getClass());
            classWriteMethodCache.put(className, writeMethodCache);
        } else {
            writeMethodCache = classWriteMethodCache.get(className);
        }
        // cacheKey必须要加上value Type
        Method method = writeMethodCache.get(name);
        if (method != null) {
            try {
                method.invoke(obj, value);
            } catch (Exception e) {
            }
        }
    }

    public static Map<String, Method> getWriteMethod(final Class cls) {
        Map<String, Method> rsMap = new HashMap<>();
        try {
            Class<?> currentClass = cls;
            while (currentClass != null) {
                //Introspector.getBeanInfo 获取的数据仅包含本类的信息,不包含继承的父类数据,所以后面需要递归取父类数据
                BeanInfo sourceBeanInfo = Introspector.getBeanInfo(currentClass);
                PropertyDescriptor[] propertyDescriptorList = sourceBeanInfo.getPropertyDescriptors();
                for (PropertyDescriptor item : propertyDescriptorList) {
                    Method m = item.getWriteMethod();
                    if (!rsMap.containsKey(item.getName()) && m != null && Modifier.isPublic(m.getModifiers())) {
                        rsMap.put(item.getName(), m);
                    }
                }
                //取父类方法
                currentClass = currentClass.getSuperclass();
            }
        } catch (Exception e) {
        }
        return rsMap;
    }
}


  • 性能对比
    • apache的属性copy很慢,不推荐使用,自己实现的BeanUtilsMy.copyProperty性能很接近直接java set(当然如果少了对属性的循环,直接使用java对象的set方法性能会好很多)
工具类数量耗时(ms)
org.apache.commons.beanutils.BeanUtils.copyProperty10w500
BeanUtilsMy.copyProperty10w18
java set10w23
org.apache.commons.beanutils.BeanUtils.copyProperty100w3434
BeanUtilsMy.copyProperty100w188
java set100w56
  • 测试代码:
 public static void copyBeanProperties(int loopNum) {

//
        try {
            Map<String, String> fieldValueMap = new HashMap<>();
            fieldValueMap.put("f1", "h~1");
            fieldValueMap.put("f2", "h~2");
            fieldValueMap.put("f3", "h~3");
            fieldValueMap.put("f4", "h~4");
            BeanDemo beanDemo = new BeanDemo();
            StopWatch sw = new StopWatch("设置Bean属性,loopNum:" + loopNum);

            sw.start("org.apache.commons.beanutils.BeanUtils.copyProperty");
            for (int i = 0; i < loopNum; i++) {
                Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, String> entry = iterator.next();
                    org.apache.commons.beanutils.BeanUtils.copyProperty(beanDemo, entry.getKey(), entry.getValue());
                }
            }
            sw.stop();
            beanDemo = new BeanDemo();
            sw.start("BeanUtilsMy.copyProperty");
            for (int i = 0; i < loopNum; i++) {
                Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, String> entry = iterator.next();
                    BeanUtilsMy.copyProperty(beanDemo, entry.getKey(), entry.getValue());
                }
            }
            sw.stop();
            beanDemo = new BeanDemo();
            sw.start("java set");
            for (int i = 0; i < loopNum; i++) {
                Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, String> entry = iterator.next();
                    if("f1".equals(entry.getKey())){
                        beanDemo.setF1(entry.getValue());
                    }else if("f2".equals(entry.getKey())){
                        beanDemo.setF2(entry.getValue());
                    }else if("f3".equals(entry.getKey())){
                        beanDemo.setF3(entry.getValue());
                    }else if("f4".equals(entry.getKey())){
                        beanDemo.setF4(entry.getValue());
                    }
                }
            }
            sw.stop();
            System.out.println(sw.prettyPrint());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

测试结果:

StopWatch '设置Bean属性,loopNum:100000': running time (millis) = 541
-----------------------------------------
ms     %     Task name
-----------------------------------------
00500  092%  org.apache.commons.beanutils.BeanUtils.copyProperty
00018  003%  BeanUtilsMy.copyProperty
00023  004%  java set

StopWatch '设置Bean属性,loopNum:1000000': running time (millis) = 3678
-----------------------------------------
ms     %     Task name
-----------------------------------------
03434  093%  org.apache.commons.beanutils.BeanUtils.copyProperty
00188  005%  BeanUtilsMy.copyProperty
00056  002%  java set
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值