lombok javabean copy异常

lombok javabean copy异常

测试案例

定义两个类:

Test1:

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;


@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Test1 {
    
    private String aaa;
}

Test2:

public class Test2 {
    
    private String aaa;

    
    /**
     * @return the aaa
     */
    public String getAaa() {
    
        return aaa;
    }

    
    /**
     * @param aaa the aaa to set
     */
    public void setAaa(String aaa) {
    
        this.aaa = aaa;
    }
}

写个测试类:

public class Test {
    
    public static void main(String[] args) {
    
        test1();
        test2();
        test3();
        test4();
    }
    
    private static void test1() {
    
        System.out.println("test1 复制到 test2");
        Test1 t1 = new Test1();
        Test2 t2 = new Test2();
        t1.setAaa("aaa");
        BeanCopier copyLog = BeanCopier.create(Test1.class, Test2.class, false);
        copyLog.copy(t1, t2, null);
        doPrint(t2.getAaa());
    }
    
    private static void test2() {
    
        System.out.println("test2 复制到 test1");
        Test1 t1 = new Test1();
        Test2 t2 = new Test2();
        t2.setAaa("aaa");
        BeanCopier copyLog = BeanCopier.create(Test2.class, Test1.class, false);
        copyLog.copy(t2, t1, null);
        doPrint(t1.getAaa());
    }

    private static void test3() {
    
        System.out.println("test1 复制到 test1");
        Test1 t1 = new Test1();
        Test1 t2 = new Test1();
        t2.setAaa("aaa");
        BeanCopier copyLog = BeanCopier.create(Test1.class, Test1.class, false);
        copyLog.copy(t2, t1, null);
        doPrint(t1.getAaa());
    }

    private static void test4() {
    
        System.out.println("test2 复制到 test2");
        Test2 t1 = new Test2();
        Test2 t2 = new Test2();
        t2.setAaa("aaa");
        BeanCopier copyLog = BeanCopier.create(Test2.class, Test2.class, false);
        copyLog.copy(t2, t1, null);
        doPrint(t1.getAaa());
    }
    
    private static void doPrint(String src) {
    
        if (StringUtil.isEmpty(src)) {
            System.out.println("复制失败,属性为空");
        }
        else {
            System.out.println("复制成功,值为:" + src);
        }
    }
}

运行结果:

test1 复制到 test2
复制成功,值为:aaa
test2 复制到 test1
复制失败,属性为空
test1 复制到 test1
复制失败,属性为空
test2 复制到 test2
复制成功,值为:aaa

分析

使用lombok生成的set方法返回不是void类型

image-20200512173752366

跟踪BeanCopier初始化过程:

BeanCopier

    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();
    }

Generator

        public void generateClass($ClassVisitor v)
        {
            $Type sourceType = $Type.getType(source);
            $Type targetType = $Type.getType(target);
            ClassEmitter ce = new ClassEmitter(v);
            ce.begin_class(46, 1, getClassName(), BeanCopier.BEAN_COPIER, null, "<generated>");
            EmitUtils.null_constructor(ce);
            CodeEmitter e = ce.begin_method(1, BeanCopier.COPY, null);
            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]);

            net.sf.cglib.core.Local targetLocal = e.make_local();
            net.sf.cglib.core.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);
            }
            for(int i = 0; i < setters.length; i++)
            {
                PropertyDescriptor setter = setters[i];
                PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
                if(getter == null)
                    continue;
                MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
                MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); // 这里是找目标对象的set方法
                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(BeanCopier.CONVERTER, BeanCopier.CONVERT);
                    e.unbox_or_zero(setterType);
                    e.invoke(write);
                    continue;
                }
                if(compatible(getter, setter))
                {
                    e.dup2();
                    e.invoke(read);
                    e.invoke(write);
                }
            }

            e.return_value();
            e.end_method();
            ce.end_class();
        }

PropertyDescriptor

    /**
     * Gets the method that should be used to write the property value.
     *
     * @return The method that should be used to write the property value.
     * May return null if the property can't be written.
     */
    public synchronized Method getWriteMethod() {
        Method writeMethod = this.writeMethodRef.get();
        if (writeMethod == null) {
            Class<?> cls = getClass0();
            if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {
                // The write method was explicitly set to null.
                return null;
            }

            // We need the type to fetch the correct method.
            Class<?> type = getPropertyType0();
            if (type == null) {
                try {
                    // Can't use getPropertyType since it will lead to recursive loop.
                    type = findPropertyType(getReadMethod(), null);
                    setPropertyType(type);
                } catch (IntrospectionException ex) {
                    // Without the correct property type we can't be guaranteed
                    // to find the correct method.
                    return null;
                }
            }

            if (writeMethodName == null) {
                writeMethodName = Introspector.SET_PREFIX + getBaseName();
            }

            Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
            if (writeMethod != null) {
                if (!writeMethod.getReturnType().equals(void.class)) {  // 这里是关键点
                    writeMethod = null;
                }
            }
            try {
                setWriteMethod(writeMethod);
            } catch (IntrospectionException ex) {
                // fall through
            }
        }
        return writeMethod;
    }

解决方法

  1. Test1改为

    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = false)
    public class Test1 {
        
        private String aaa;
    }
    
  2. 使用原始的get/set方法

结论

lombok使用的时候一定要注意参数的定义,风险较高

posted @ 2021-08-10 15:29  又见君  阅读(87)  评论(0)    收藏  举报