众所周知,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
属性没有被覆盖掉,输出结果非常理想
生成的字节码也如我们所想,正确传递了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;
}
也没问题,搞定
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();
}
}