扩展BeanCopier实现,增强版Converter,支持@Accessors(chain = true)

2,107 阅读4分钟

众所周知,BeanCopier 是使用编织字节码的方式实现的bean属性复制,速度基本上可以持平是手写转换类的水平,但是spring 的cglib BeanCopier 虽然有一个 Converter接口,但实际上convert并不传递 target 对应的字段属性,所以没有办法利用 Converter 接口任意定制bean的复制规则

package org.springframework.cglib.core;

public interface Converter {
/**
 * @param var1 sourceFiled 源属性
 * @param var2 targetFiledClass 目标属性的class
 * @param var3 targetFiledSetterName 目标属性的setter方法的方法名
 * @return 设置目标的属性
 */
    Object convert(Object var1, Class var2, Object var3);
}

通过在Test中测试,我们可以在对应的D盘的文件中找到class文件(一共两个,找到以Object开头的)

@Test
void copyFiledd(){
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\testDebug");
    SimpleObj source = new SimpleObj();source.setId(1);
    SimpleObj target = new SimpleObj();target.setId(2);target.setName("no2");
    BeanCopier beanCopier = BeanCopier.create(SimpleObj.class, SimpleObj.class, true);
    beanCopier.copy(source,target,(a,b,c)-> a);
    System.out.println(target);
}
@Data
public static class SimpleObj{
    private Integer id;
    private String name;
}

class文件拖入idea之后,查看反编译的java代码是这样的,我们可以看到,实际上就是将三个参数传递到了Converter 中执行获取返回值并setter对应的target属性里

public class Object$$BeanCopierByCGLIB$$d7a2ef17 extends BeanCopier {
    private static final Class CGLIB$load_class$java$2Elang$2EInteger;
    private static final Class CGLIB$load_class$java$2Elang$2EString;

    public Object$$BeanCopierByCGLIB$$d7a2ef17() {
    }
    //跟手写转换类差不多
    public void copy(Object var1, Object var2, Converter var3) {
        SimpleObj var4 = (SimpleObj)var2;
        SimpleObj var5 = (SimpleObj)var1;
        var4.setId((Integer)var3.convert(var5.getId(), CGLIB$load_class$java$2Elang$2EInteger, "setId"));
        var4.setName((String)var3.convert(var5.getName(), CGLIB$load_class$java$2Elang$2EString, "setName"));
    }

    static void CGLIB$STATICHOOK1() {
        CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
        CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

于是,我们新建一个新的 CopyConverter 将目标属性传递进来

@FunctionalInterface
public interface CopyConverter {
    /**
     * @param sourceFiled 源属性
     * @param targetFiledClass 目标属性的class
     * @param targetFiledSetter 目标属性的setter方法的方法名
     * @param targetFiled 目标属性
     * @return 设置目标的属性
     */
    Object convert(Object sourceFiled, Class<?> targetFiledClass, Object targetFiledSetter,Object targetFiled);
}

然后仿照BeanCopier源码,写一个 BeanCopierPlus

import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.KeyFactory;
import org.springframework.cglib.core.Local;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;

public abstract class BeanCopierPlus {
    private static final BeanCopierPlusKey KEY_FACTORY = (BeanCopierPlusKey)KeyFactory.create(BeanCopierPlusKey.class);
    private static final Type CONVERTER = TypeUtils.parseType(CopyConverter.class.getCanonicalName());
    private static final Type BEAN_COPIER = TypeUtils.parseType(BeanCopierPlus.class.getCanonicalName());
    private static final Signature COPY;
    private static final Signature CONVERT;

    public BeanCopierPlus() {
    }

    public static BeanCopierPlus create(Class<?> source, Class<?> target, boolean useConverter) {
        BeanCopierPlus.Generator gen = new BeanCopierPlus.Generator();
        gen.setSource(source);
        gen.setTarget(target);
        gen.setUseConverter(useConverter);
        return gen.create();
    }

    public abstract void copy(Object source, Object target, CopyConverter converter);

    static {
        //如果更改了copy的方法签名,记得改这里
        COPY = new Signature("copy", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER});
        CONVERT = ReflectUtils.getSignature(CopyConverter.class.getMethods()[0]);
    }

    public static class Generator extends AbstractClassGenerator<BeanCopierPlus> {
        private static final Source SOURCE = new Source(BeanCopierPlus.class.getName());
        private Class<?> source;
        private Class<?> target;
        private boolean useConverter;

        public Generator() {
            super(SOURCE);
        }

        public void setSource(Class<?> source) {
            if (!Modifier.isPublic(source.getModifiers())) {
                this.setNamePrefix(source.getName());
            }
            this.source = source;
        }

        public void setTarget(Class<?> target) {
            if (!Modifier.isPublic(target.getModifiers())) {
                this.setNamePrefix(target.getName());
            }
            this.target = target;
        }

        public void setUseConverter(boolean useConverter) {
            this.useConverter = useConverter;
        }

        protected ClassLoader getDefaultClassLoader() {
            return this.source.getClassLoader();
        }

        protected ProtectionDomain getProtectionDomain() {
            return ReflectUtils.getProtectionDomain(this.source);
        }

        public BeanCopierPlus create() {
            Object key = BeanCopierPlus.KEY_FACTORY.newInstance(this.source.getName(), this.target.getName(), this.useConverter);
            return (BeanCopierPlus)super.create(key);
        }
        @Override
        public void generateClass(ClassVisitor v) {
            Type sourceType = Type.getType(this.source);
            Type targetType = Type.getType(this.target);
            ClassEmitter ce = new ClassEmitter(v);
            //开始“写”类,这里有修饰符、类名、父类等信息
            ce.begin_class(Opcodes.V1_8, Opcodes.ACC_PUBLIC, this.getClassName(), BeanCopierPlus.BEAN_COPIER, null, Constants.SOURCE_FILE);
            //没有构造方法
            EmitUtils.null_constructor(ce);
            //开始“写”一个方法,方法名是copy
            CodeEmitter emitter = ce.begin_method(Opcodes.ACC_PUBLIC, BeanCopierPlus.COPY, null);
            //通过 Introspect 获取source类和target类的PropertyDescriptor
            PropertyDescriptor[] setters = getBeanSetters(this.target);
            PropertyDescriptor[] targetGetters = getBeanGetters(this.target);
            Map<String,PropertyDescriptor> getterMap = new HashMap<>();
            Map<String,PropertyDescriptor> targetGetterMap = new HashMap<>();

            for (PropertyDescriptor propertyDescriptor : getters) {
                getterMap.put(propertyDescriptor.getName(), propertyDescriptor);
            }
            for (PropertyDescriptor propertyDescriptor : targetGetters) {
                targetGetterMap.put(propertyDescriptor.getName(), propertyDescriptor);
            }

            Local targetLocal = emitter.make_local();
            Local sourceLocal = emitter.make_local();
            if (this.useConverter) {
                //加载本地变量 targetLocal
                emitter.load_arg(1);
                emitter.checkcast(targetType);
                emitter.store_local(targetLocal);
                //加载本地变量 sourceLocal
                emitter.load_arg(0);
                emitter.checkcast(sourceType);
                emitter.store_local(sourceLocal);
            } else {
                emitter.load_arg(1);
                emitter.checkcast(targetType);
                emitter.load_arg(0);
                emitter.checkcast(sourceType);
            }
            //通过属性名来生成转换的代码
            //以setter作为遍历
            for (PropertyDescriptor setter : setters) {
                PropertyDescriptor getter = getterMap.get(setter.getName());
                PropertyDescriptor targetGetter = targetGetterMap.get(setter.getName());
                if (getter != null) {
                    MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
                    MethodInfo targetRead = ReflectUtils.getMethodInfo(targetGetter.getReadMethod());
                    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
                    if (this.useConverter) {
                        Type setterType = write.getSignature().getArgumentTypes()[0];
                        //加载局部变量 targetLocal
                        emitter.load_local(targetLocal);
                        //加载copy方法的第3个参数 即CopyConverter
                        emitter.load_arg(2);
                        //加载局部变量sourceLocal
                        emitter.load_local(sourceLocal);
                        //执行 局部变量 sourceLocal 的getter方法
                        emitter.invoke(read);
                        //如果是基本类型,将其转换为包装类型
                        emitter.box(read.getSignature().getReturnType());
                        //加载class变量
                        EmitUtils.load_class(emitter, setterType);
                        //加载String参数
                        emitter.push(write.getSignature().getName());
                        //加载局部变量
                        emitter.load_local(targetLocal);
                        //局部变量执行get方法
                        emitter.invoke(targetRead);
                        //如果是基本类型,将其转换为包装类型
                        emitter.box(read.getSignature().getReturnType());
                        //加载 CONVERTER 接口的 CONVERT 方法,注意 CONVERT 中的方法参数
                        emitter.invoke_interface(BeanCopierPlus.CONVERTER, BeanCopierPlus.CONVERT);
                        //拆箱或者null时归零
                        emitter.unbox_or_zero(setterType);
                        //执行setter方法,此时栈内变量为 targetLocal
                        emitter.invoke(write);
                        // setter方法有返回值时,弹出堆栈
                        if (!write.getSignature().getReturnType().equals(Type.VOID_TYPE)) {
                            emitter.pop();
                        }
                    } else if (compatible(getter, setter)) {
                        emitter.dup2();
                        emitter.invoke(read);
                        emitter.invoke(write);
                        // setter方法有返回值时,弹出堆栈
                        if (!write.getSignature().getReturnType().equals(Type.VOID_TYPE)) {
                            emitter.pop();
                        }
                    }
                }
            }

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

        private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
            return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
        }
        
//这里直接为了方便展示,把Util方法挪进来了,用的时候可以放到自己的Util里
        public static PropertyDescriptor[] getBeanGetters(Class<?> type) {
            return getPropertiesHelper(type, true, false);
        }
//这里直接为了方便展示,把Util方法挪进来了,用的时候可以放到自己的Util里
        public static PropertyDescriptor[] getBeanSetters(Class<?> type) {
            return getPropertiesHelper(type, false, true);
        }
//这里直接为了方便展示,把Util方法挪进来了,用的时候可以放到自己的Util里
        /**
         * 支持如Lombok的@Accessors 注解的 chain setter
         */
        @SneakyThrows
        private static PropertyDescriptor[] getPropertiesHelper(Class<?> type, boolean read, boolean write) {
            BeanInfo info = Introspector.getBeanInfo(type, Object.class);
            PropertyDescriptor[] all = info.getPropertyDescriptors();

            List<PropertyDescriptor> properties = new ArrayList<>(all.length);
            for (PropertyDescriptor pd : all) {
                if ((read && pd.getReadMethod() != null) || (write && pd.getWriteMethod() != null)) {
                    properties.add(pd);
                }
            }
            if (write) {
                // Check for fluent setters.
                Method[] methods = type.getMethods();
                for (Method method : methods) {
                    if (method.getParameterTypes().length == 1
                            && method.getReturnType().equals(type)
                            // 如果想支持 @Accessors(fluent = true) 可以把set判断去掉
                            && method.getName().startsWith("set")) {
                        PropertyDescriptor pd = new PropertyDescriptor(getFieldName(method.getName()), null, method);
                        properties.add(pd);
                    }
                }
            }
            return properties.toArray(new PropertyDescriptor[0]);
        }
//这里直接为了方便展示,把Util方法挪进来了,用的时候可以放到自己的Util里
        private static String getFieldName(String methodName) {
            if (methodName.startsWith("get") && methodName.length() > 3) {
                return Introspector.decapitalize(methodName.substring(3));
            }
            if (methodName.startsWith("set") && methodName.length() > 3) {
                return Introspector.decapitalize(methodName.substring(3));
            }
            if (methodName.startsWith("is") && methodName.length() > 2) {
                return Introspector.decapitalize(methodName.substring(2));
            }
            return Introspector.decapitalize(methodName);
        }

        protected Object firstInstance(Class type) {
            return ReflectUtils.newInstance(type);
        }

        protected Object nextInstance(Object instance) {
            return instance;
        }
    }

    interface BeanCopierPlusKey {
        Object newInstance(String var1, String var2, boolean var3);
    }
}

使用我们的 BeanCopierPlus 再进行测试

@Test
void copyField(){
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\testDebug");
    SimpleObj source = new SimpleObj();source.setId(1);
    SimpleObj target = new SimpleObj();target.setId(2);target.setName("no2");
    BeanCopierPlus beanCopier = BeanCopierPlus.create(SimpleObj.class, SimpleObj.class, true);
    beanCopier.copy(source,target,(a,b,c,d)-> a==null?d:a);
    System.out.println(target);
}
@Data
public static class SimpleObj{
    private Integer id;
    private String name;
}

target的name属性没有被覆盖掉,输出结果非常理想

image.png

生成的字节码也如我们所想,正确传递了target的属性

public class Object$$BeanCopierPlusByCGLIB$$d7a2ef17 extends BeanCopierPlus {
    private static final Class CGLIB$load_class$java$2Elang$2EInteger;
    private static final Class CGLIB$load_class$java$2Elang$2EString;

    public Object$$BeanCopierPlusByCGLIB$$d7a2ef17() {
    }

    public void copy(Object var1, Object var2, CopyConverter var3) {
        SimpleObj var4 = (SimpleObj)var2;
        SimpleObj var5 = (SimpleObj)var1;
        var4.setId((Integer)var3.convert(var5.getId(), CGLIB$load_class$java$2Elang$2EInteger, "setId", var4.getId()));
        var4.setName((String)var3.convert(var5.getName(), CGLIB$load_class$java$2Elang$2EString, "setName", var4.getName()));
    }

    static void CGLIB$STATICHOOK1() {
        CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
        CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

再测试一下@Accessors(chain = true)

@Test
void copyField(){
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\testDebug");
    SimpleObj source = new SimpleObj();source.setId(1);
    SimpleObj target = new SimpleObj();target.setId(2);target.setName("no2");
    BeanCopierPlus beanCopier = BeanCopierPlus.create(SimpleObj.class, SimpleObj.class, true);
    beanCopier.copy(source,target,(a,b,c,d)-> a==null?d:a);
    System.out.println(target);
}
@Data
@Accessors(chain = true)
public static class SimpleObj{
    private Integer id;
    private String name;
}

image.png

也没问题,搞定

public class Object$$BeanCopierPlusByCGLIB$$ecb90459 extends BeanCopierPlus {
    private static final Class CGLIB$load_class$java$2Elang$2EInteger;
    private static final Class CGLIB$load_class$java$2Elang$2EString;

    public Object$$BeanCopierPlusByCGLIB$$ecb90459() {
    }

    public void copy(Object var1, Object var2, CopyConverter var3) {
        SimpleObj var4 = (SimpleObj)var2;
        SimpleObj var5 = (SimpleObj)var1;
        var4.setId((Integer)var3.convert(var5.getId(), CGLIB$load_class$java$2Elang$2EInteger, "setId", var4.getId()));
        var4.setName((String)var3.convert(var5.getName(), CGLIB$load_class$java$2Elang$2EString, "setName", var4.getName()));
    }

    static void CGLIB$STATICHOOK1() {
        CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
        CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");
    }

    static {
        CGLIB$STATICHOOK1();
    }
}