java:asm实现ResultSet结果映射到实体类

java:asm实现ResultSet结果映射到实体类

1 前言

Spring-core包中提供了许多方便的工具类,其中org.springframework.cglib.beans下的BeanCopier工具类,主要用于bean之间的属性拷贝,性能上优于Spring-beans包下的org.springframework.beans.BeanUtils的copyProperties方法属性拷贝。因为BeanCopier的属性拷贝,本质上是通过asm,即java字节码来动态生成bean的get、set赋值的方法,相比于BeanUtils的copyProperties方法直接通过反射调用bean的getter和setter方法达到拷贝效果来说,生成java字节码方法并调用,是更为轻量级的方式,效率也更高。

本文主要通过源码分析和源码实战拓展,来分析通过Spring集成的asm框架技术,自定义实现ResultSet结果映射到实体类的高效方式。

源码参考SpringBoot依赖如下:

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.5.4</version>
</parent>

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

具体源码版本查看:

idea中ctrl + 鼠标左键点击:spring-boot-starter-web,从pom文件可知spring-web依赖版本为5.3.9,同理ctrl+鼠标左键点击spring-web,可知参考源码的spring-core包的依赖版本为5.3.9:

在这里插入图片描述

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.3.9</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.9</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

2 使用

2.1 maven依赖中源码查看

idea中,按下shift键,同时键盘输入:Spring-core,可以快速搜索到源码所在位置:

在这里插入图片描述

2.2 spring-core源码分析 - BeanCopier

BeanCopier简单使用demo:

package com.xiaoxu.demo.Beans;

import lombok.Data;
import lombok.ToString;
import org.springframework.cglib.beans.BeanCopier;

/**
 * @author xiaoxu
 * @date 2023-06-27 23:03
 * spring_boot:com.xiaoxu.demo.Beans.BeanCopierTest
 */
public class BeanCopierTest {

    public static void main(String[] args) {
        test1();
//        test2();
    }

    public static void test1() {
        System.getProperties().put("cglib.debugLocation", 
        "src\\main\\java\\com\\xiaoxu\\demo\\Beans\\writer");
        BeanCopier beanCopier = BeanCopier.create(ADemo.class, ADemo.class, false);
        ADemo a = new ADemo();
        a.setName("ademo");
        a.setAge(40);
        ADemo b = new ADemo();
        System.out.println("初始:" + a + ";" + b);
        beanCopier.copy(a, b,
                null);
        System.out.println("结束:" + a + ";" + b);
    }

    public static void test2() {
        BeanCopier beanCopier = BeanCopier.create(ADemo.class, BDemo.class, false);
        ADemo a = new ADemo();
        a.setName("ademo");
        a.setAge(40);
        BDemo b = new BDemo();
        System.out.println("初始:" + a + ";" + b);
        beanCopier.copy(a, b,
                null);
        System.out.println("结束:" + a + ";" + b);
    }

}

@Data
@ToString
class BDemo {
    public String name;
    public int age;
    public String bName;
    public long bAge;
}

@Data
@ToString
class ADemo {
    public String name;
    public int age;
}

执行结果如下:

CGLIB debugging enabled, writing to 'src\main\java\com\xiaoxu\demo\Beans\writer'
初始:ADemo(name=ademo, age=40);ADemo(name=null, age=0)
结束:ADemo(name=ademo, age=40);ADemo(name=ademo, age=40)

源码片段示例:

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

源码分析:

本质上,BeanCopier的create方法逻辑,是由BeanCopier的静态内部类Generator实现。而内部类Generator的generateClass(ClassVisitor v)方法,是BeanCcopier的核心逻辑实现,即通过asm生成Class,该Class的父类为BeanCopier,所以Spring框架将BeanCopier类设置为抽象类(当然主要目的是为了使用asm实现BeanCopier类中的抽象方法:public abstract void copy(Object var1, Object var2, Converter var3)),generateClass方法源码片段如下:

ce.begin_class(52, 1, this.getClassName(), BeanCopier.BEAN_COPIER, 
(Type[])null, "<generated>");

其中第一个参数52指的是JDK版本,即int V1_8 = 52;,说明是JDK1.8版本;

第二个参数为access,即Class的Modifier,1说明是public class(ACC_PUBLIC),其余常见如ACC_PRIVATE,值为2,表示private,ACC_PROTECTED值为4,表示protected等等;

第三个参数是该Class的name,提到该值,那么就需要提及Generator类的父类AbstractClassGenerator,该类是Spring多个实现asm动态生成java字节码的关键父类,它有一个protected Class generate(ClassLoaderData data)方法,该方法自然被BeanCopier的静态内部类Generator所继承使用,其中就设置了class name,早于byte[] b = strategy.generate(this)的调用,即早于this.transform(cg).generateClass(cw)的调用,所以在生成Class字节数组前,已经为Class生成了Name:

部分代码片段如下:

synchronized (classLoader) {
	String name = generateClassName(data.getUniqueNamePredicate());
	data.reserveName(name);
	this.setClassName(name);
}

而generateClassName方法,默认使用的是DefaultNamingPolicy来生成Class Name:

private String generateClassName(Predicate nameTestPredicate) {
	return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate);
}

第四个参数,也就是上述提到的,该Class的super class,设置为BeanCopier.BEAN_COPIER,即TypeUtils.parseType(“org.springframework.cglib.beans.BeanCopier”),它是Object类型,且存入了asm所需要的descriptor参数等,举例来说比如java.lang.String,那么它的descriptor是:(Ljava/lang/String;),即括号中的表达式,而在ClassEmitter类中,begin_class方法实际使用的是该super Type的internalName,即org/springframework/cglib/beans/BeanCopier,因为Object类型会截取1到length-1,即去掉开头的(L)和末尾的(;),它会作为UTF-8编码的字符串被存放在Class文件的常量池中;

第五个参数是Type数组,即该Class的实现接口,因为java是单继承多实现,所以父类只有一个,而接口是Type数组参数,这里没有实现接口故而为(Type[])null;

第六个参数是source,供visitSource方法使用,意即(the name of the source file from which the class was compiled. May be null),编译类的源文件名称,例如:BeanCopierTest.java等,同visitSource方法的debug参数,均可为null,这里Spring默认填充值为< generated >。

当然,上述提到的BeanCopier的抽象方法void copy(Object var1, Object var2, Converter var3)是核心的逻辑,需重点分析。不过在分析copy方法的逻辑前,Spring还有一个步骤,即在执行begin_class后,还为该Class创建了无参构造,即:(EmitUtils.null_constructor(ce);)。基本Spring中asm生成的Class都会为该Class加上无参构造,这是为了方便后续调用firstInstance(Class type)方法。

承上,因为firstInstance方法中调用了ReflectUtils.newInstance(type),逻辑是获取Class的无参构造方法,并通过构造方法执行newInstance()生成对象,所以该步骤重要且不可忽略(一般常用的Class对象.newInstance生成对象,也是依赖无参构造方法生成对象,若不存在该无参构造,则抛出异常NoSuchMethodException)。

void generateClass(ClassVisitor v)方法完整代码片段:

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(52, 1, this.getClassName(), BeanCopier.BEAN_COPIER, (Type[])null, "<generated>");
    EmitUtils.null_constructor(ce);
    CodeEmitter e = ce.begin_method(1, BeanCopier.COPY, (Type[])null);
    PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(this.source);
    PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(this.target);
    Map names = new HashMap();

    for(int i = 0; i < getters.length; ++i) {
        names.put(getters[i].getName(), getters[i]);
    }

    Local targetLocal = e.make_local();
    Local sourceLocal = e.make_local();
    if (this.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) {
            MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
            MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
            if (this.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);
            } else if (compatible(getter, setter)) {
                e.dup2();
                e.invoke(read);
                e.invoke(write);
            }
        }
    }

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

可见无参构造创建后,紧接着调用:begin_method(1, BeanCopier.COPY, (Type[])null),用于声明copy方法的字节码Modifier、方法名称、descriptor等等。

参数说明:

1代表方法为public method,因为生成的Class对象继承了抽象类BeanCopier,该方法是对父类抽象方法copy的重写,故而仅为public;

BeanCopier.COPY为Signature对象,值为:new Signature(“copy”, Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER}),可知Signature对象的name为copy。

Type.VOID_TYPE的internalName为V,同时该Signature的第三个参数为方法的参数Type[]数组,通过Type.getMethodDescriptor方法,可以获取该方法的descriptor,故可得Signature的desc为:(Ljava/lang/Object;Ljava/lang/Object;Lorg/springframework/cglib/core/Converter;)V。

第三个参数是该方法需要显式声明抛出的异常Type数组,比如一般定义方法,如果需要外部调用该方法的对象去处理异常,那么形如:public void getName() throws IllegalStateException, NestedRuntimeException {},这里的exception的Type数组,就是为了获取IllegalStateException、NestedRuntimeException的internalName数组。因为copy方法无需显示抛出异常由调用者处理,所以这里为(Type[])null。

那么接下来的实现逻辑就很清晰了,即通过java字节码指令(java virtual machine(jvm) bytecode instruction),实现copy方法的内容。

首先通过ReflectUtils.getBeanGetters获取source,即源Bean Class的getter方法PropertyDescriptor对象,ReflectUtils.getBeanSetters获取target,目标Bean Class的setter方法PropertyDescriptor对象,并将getter方法的字段名作为map的key,value为对应的PropertyDescriptor对象,存入names map中,留待后续使用。

这里getters[i].getName(),实际举例来说,如上述提供的ADemo类,存入的就是字段名称如:name、age。这个处理逻辑,在后续逻辑中,对于:PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());,在getter != null的情况下,才会进行bean的属性拷贝,意即BeanCopier进行复制拷贝的源Bean和目标Bean,它们的字段名称首先必须一致,才可能进行属性拷贝,此为首要条件。

PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(this.source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(this.target);
Map names = new HashMap();

for(int i = 0; i < getters.length; ++i) {
    names.put(getters[i].getName(), getters[i]);
}

java字节码指令的使用方式及含义,可参考官方文档如下:

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5

上述定义了getter方法的Map映射集,接下来是对copy方法局部变量的定义,即make_local()、store_local()等。

Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (this.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);
}

在分析上述对局部变量的使用前,首先明确java字节码中,对于方法的局部变量使用。首先定义一个run方法,然后通过javap -verbose,打印出该class文件的字节码分析(idea鼠标右键java文件,open in -> terminal即可执行如下两条指令):

public class ByteCodeDemo {
    public void run(String name, long age, Object obj, int num) {
        String name_temp = name;
        long age_temp = age;
        Object obj_temp = obj;
        int num_temp = num;
    }
}

在这里插入图片描述

idea展示的class文件形如:

public class ByteCodeDemo {
    public ByteCodeDemo() {
    }

    public void run(String var1, long var2, Object var4, int var5) {
    }
}
public void run(java.lang.String, long, java.lang.Object, int);
  descriptor: (Ljava/lang/String;JLjava/lang/Object;I)V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=11, args_size=5
       0: aload_1
       1: astore        6
       3: lload_2
       4: lstore        7
       6: aload         4
       8: astore        9
      10: iload         5
      12: istore        10
      14: return
    LineNumberTable:
      line 10: 0
      line 11: 3
      line 12: 6
      line 13: 10
      line 14: 14

观察字节码可知,上述对于方法的第一个参数name,字节码指令是aload_1,之所以不是aload_0,是因为java的实例方法中,aload_0表示把this入栈,所以该位置已经被this所占据,故而一般方法中形参一般以1开始。Xload系列的指令,本质就是把局部变量,local variable,push onto operand stack,也就是放入操作数栈中(其中aload是针对局部变量,引用对象reference入栈的,又比如iload,是针对int类型(primitive类型的,如果是包装类,那么直接使用aload即可)的对象入栈的,lload针对long型对象等等)。栈是先进后出的,所以java字节码指令,本质就是通过操作数入栈后,通过字节码指令操作入栈的数据,或使栈顶数据出栈,执行代码逻辑。

常见的方法调用的字节码指令,有:

INVOKEVIRTUAL(182),用于执行类的实例方法,先入栈实例引用对象,再按照方法参数从左往右的顺序依次入栈参数,最后调用字节码指令invoke_virtual即可,如果该实例方法有返回值,那么pop实例对象和全部方法参数后,会入栈方法的返回结果至栈顶;

INVOKESPECIAL(183),常见执行类的构造方法,配置NEW一同使用,一般实例对象NEW的时候,需要调用无参构造方法< init >,调用该无参构造方法,即可使用invoke_special;

INVOKESTATIC(184),调用类的static静态方法,用法类似invoke_virtual;

INVOKEINTERFACE(185),调用对象的接口方法,比如:List< String > x = new ArrayList<>();,若此时入栈为x引用对象,需要调用add方法,那么再入栈add方法的String对象(如果是字符串常量,或者一般的int常量等等,可以使用LDC系列的指令,从运行时常量池中,push常量数据入栈,如LDC(18)、LDC_W(19)、LDC2_W(20,用于long、double等)),再次调用invoke_interface即可,如果调用invoke_virtual等指令,在调用defineClass方法时,将字节码数组转换为Class对象时,会有对应的报错提示。

继续上述字节码的分析,第二个参数是long型的参数,它的偏移地址,应该从上个对象,并加上它的slot插槽的大小作为地址,因为引用对象,Object类型的参数的slot插槽所占大小为1,所以字节码指令为:lload_2,即(aload_1的1,+Object的slot大小为1,故而是:lload_2);但是接下来的local variable,第三个参数是Object,指令是:aload 4,由上述说明可知,primitive类型的long型变量,它的slot插槽大小是2,所以才会得到下个local variable的偏移是aload 4(lload_2的2,+long的slot大小2),其实根据文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5亦可知,可查看如dup2指令,提到的a category 1 computational type,第一类计算类型和a category 2 computational type,第二类计算类型,其中primitive类型的long和double,slot都是2,而其余primitive类型的boolean、char、byte、short、int、float这6种,还有reference即引用对象,和returnAddress,slot均为1,如下图所示:

在这里插入图片描述
故而可以从Spring的org.springframework.asm.Type类中的getSize方法,亦可得如上的逻辑体现:

public int getSize() {
  switch (sort) {
    case VOID:
      return 0;
    case BOOLEAN:
    case CHAR:
    case BYTE:
    case SHORT:
    case INT:
    case FLOAT:
    case ARRAY:
    case OBJECT:
    case INTERNAL:
      return 1;
    case LONG:
    case DOUBLE:
      return 2;
    default:
      throw new AssertionError();
  }
}

可见获取Type的size大小时,primitive类型的long和double,大小均为2,其余primitive类型,以及引用对象,都是1。

由此回归到上述对于make_local()和store_local()方法的讲解,其实现的逻辑,就是上述说明的方式,Local对象存入的是根据对象的slot大小计算出的数字下标,由此来调用local variable入操作数栈,或者Xstore指令,操作数栈中pop后并转换为local variable。

特殊说明下checkcast指令,是对象的强制类型转换,java中,父类引用指向子类对象,是向下转型,区别于子类引用指向父类对象的向上转型,向下转型是需要强制类型转换的,这里因为copy方法的bean参数均为Object,需将其转换成传入的Class类型的对象,故需使用强制类型转换。

下述核心拷贝逻辑需要分为两部分讲解,一是使用Converter的场景,即this.useConverter为true:

此时当source Bean和target Bean的属性字段名称一致时,获取target Bean的setter的参数类型,即setterType,e.load_arg(2)表示Converter对象入栈,因为copy方法的第3个参数为Converter,load_arg方法是根据参数数组下标获取method local variable,同时使其入操作数栈;

e.load_local(sourceLocal)表示source Bean入栈,然后调用其get方法,pop source Bean,同时使得对应get方法返回值入栈;

e.box(read.getSignature().getReturnType())用于将source Bean的primitive的get方法返回值,new为包装类,如果原本不为primitive,那么无任何操作;

EmitUtils.load_class(e, setterType)是为了将Class.forName()获取的target Bean的setter方法参数的Class对象,由static方法加载,故增加static方法的hook,同时将该Class对象入栈(static hook是为了减少Class.forName()方法的调用耗时,类中仅加载一次);

e.push(write.getSignature().getName()),也就是target Bean的setter方法的方法名称字符串常量,使其入栈;

故上述分析的数据,由栈底至栈顶顺序来看,应该入栈顺序依次为:Converter实例对象,对应source Bean的getter方法返回值,primitive则为包装类对象,target Bean的setter方法参数的Class对象,以及target Bean的setter字符串方法名称,故而接下来调用e.invoke_interface(BeanCopier.CONVERTER, BeanCopier.CONVERT),意即调用Converter的convert方法,所以convert方法Object convert(Object, Class, Object)的参数如下图所示:

在这里插入图片描述

返回结果Object,是重写提供的Converter的convert的返回值,并使其入栈。

注意:上述分析可知,自定义Converter使用时,第1个参数因为是getter返回值,若primitive则返回其包装类对象,若其为引用对象,那么可能为null,如果直接var1.getClass()判断数据类型可能导致NPE,故而建议直接使用var2,setter方法的参数类型即可,或者可以使用instanceof,因为我们知道,instanceof使用时,若对象 instanceof XX时,对象为null,那么返回也为false。当然,直接对var1增加非空判断的处理也是不错的。

接下来分析e.unbox_or_zero(setterType):

若setter方法参数为primitive,则对convert方法的返回值Object,进行非空判断,若为null,则将null值出栈,并根据不同的setterType,进行特殊拆箱处理,如int则入栈0,float则入栈0.0F常量,long则入栈0L,double则入栈0.0D等primitive类型的初始值,因为我们知道,拆箱时,若直接把null赋值给primitive的类型,会抛出异常,应该转换成primitive类型未赋值时的初始值;若不为null,那么说明convert方法的返回值Object应当是1个包装类对象,由此才可以赋值给primitive的setter方法,故而接下来就是调用各包装类的方法返回其primitive的值,如Integer的方法int intValue()、Double的double doubleValue()等等,并将结果入栈;

若setter方法参数非primitive,如Array、Object,则仅对convert方法的返回值Object,做强制类型转换,转换为setter方法的参数类型即可,若setter方法参数也为Object,则不做处理。

最后调用e.invoke(write),调用target Bean的setter方法,为其赋值,并继续下一次循环。

for(int i = 0; i < setters.length; ++i) {
    PropertyDescriptor setter = setters[i];
    PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
    if (getter != null) {
        MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
        MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
        if (this.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);
        } else if (compatible(getter, setter)) {
            e.dup2();
            e.invoke(read);
            e.invoke(write);
        }
    }
}

上述主要针对使用了Converter的场景,若未使用Converter,则compatible(getter, setter)方法判断setter的类型,必须是getter返回值的父类型或相同类型,当然也不能有一方为null(否则直接抛出NPE)。举例来说,假设get返回值类型是String,而set的接收参数也是String,或者set的接收参数是Object,或者说是String的父类类型,比如CharSequence,则满足赋值的前提条件。也就是说,不使用Converter的场景下,source Bean和target Bean的字段名称需一致(比如字段都是:fruitName),且set字段的类型和get返回值类型一致(比如字段类型均为:String),或者是其父类类型(比如set字段类型为CharSequence,get的字段类型为String),则赋值成功。当然,此场景就不会涉及到包装类和primitive类型之间的转换了,因为compatible(getter, setter)方法直接不满足。

在满足赋值的前提条件下,dup2指令负责复制栈顶最前面的两个slot大小为1的对象,从栈顶往栈底方向,首先是source Bean,再是target Bean,因此先调用source Bean的getter方法并使返回值入栈,再pop栈顶的getter返回值和复制的target Bean,调用其setter方法为其赋值,setter方法没有返回值(void)。后续for循环中,同理满足条件下,再次复制栈顶最前面的两个slot大小为1的对象,即source Bean和target Bean,继续循环赋值即可。

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

最后e.return_value()就是根据copy方法的返回值类型,执行Xreturn指令结束整个方法,而ce.end_class()则是需要对可能存在的static hook方法,即static方法执行end操作,通过Xreturn指令结束方法,并完成整个类,到此整个逻辑已分析完毕。

2.3 spring-asm拓展,通过asm实现ResultSet结果映射到实体类

有了上述的源码分析基础,那么我们不难通过Spring提供的工具API,使用字节码自定义实现高性能的ResultSet结果集映射到实体类的方法。

如下定义Transition工具类,用于将ResultSet结果集映射到实体类,命名的前提是表的字段名称,下划线转为小驼峰后,可以与目标的实体类字段名称一致,且字段的Class类型是适配的,下述的方法实现效果并未是目标功能,我们来观察下这样写存在什么问题:

package trans;

import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Label;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.cglib.core.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * @author xiaoxu
 * @date 2023-07-25
 */
@SuppressWarnings({"all"})
public abstract class Transition {

    private static final String underLineMark = "_";

    private static final TransitionKey KEY_FACTORY = (TransitionKey) KeyFactory.create(TransitionKey.class);
    private static final Type TRANSITION = TypeUtils.parseType("trans.Transition");
    private static final Type RESULT_SET = TypeUtils.parseType("java.sql.ResultSet");
    private static final Type RESULT_SET_METADATA = TypeUtils.parseType("java.sql.ResultSetMetaData");
    private static final Type LIST = TypeUtils.parseType("java.util.List");
    private static final Type MAP = TypeUtils.parseType("java.util.Map");
    private static final Type HASH_MAP = TypeUtils.parseType("java.util.HashMap");
    private static final Type PROPERTY_DESCRIPTOR = TypeUtils.parseType("java.beans.PropertyDescriptor");

    private static final Signature TRANSFER;
    private static final Signature FETCH_METADATA;
    private static final Signature FETCH_METADATA_COUNT;
    private static final Signature FETCH_METADATA_CLASS;
    private static final Signature FETCH_METADATA_COLUMN_NAME;
    private static final Signature NEXT;
    private static final Signature FOR_NAME = TypeUtils.parseSignature("Class forName(String)");
    private static final Signature PUT = new Signature("put", Constants.TYPE_OBJECT, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT});
    private static final Signature GET = new Signature("get", Constants.TYPE_OBJECT, new Type[]{Constants.TYPE_OBJECT});
    private static final Signature GET_PROPERTY_DESC = TypeUtils.parseSignature("java.util.Map getPropertyDesc(Class)");
    private static final Signature COMPATIBLE;

    private static final Signature INVOKE_HUMP = TypeUtils.parseSignature("String underlineTransferSmallHump(String)");

    private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);

    public Transition() {
    }

    static {
        TRANSFER = new Signature("transfer", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT});
        FETCH_METADATA = TypeUtils.parseSignature("java.sql.ResultSetMetaData getMetaData()");
        FETCH_METADATA_COUNT = new Signature("getColumnCount", Type.INT_TYPE, new Type[0]);
        FETCH_METADATA_CLASS = new Signature("getColumnClassName", Constants.TYPE_STRING, new Type[]{Type.INT_TYPE});
        FETCH_METADATA_COLUMN_NAME = new Signature("getColumnName", Constants.TYPE_STRING, new Type[]{Type.INT_TYPE});
        NEXT = new Signature("next", Type.BOOLEAN_TYPE, new Type[0]);
        COMPATIBLE = new Signature("isCompatible", Type.BOOLEAN_TYPE,
                new Type[]{Constants.TYPE_CLASS, PROPERTY_DESCRIPTOR});

        // Map entry iteration is less expensive to initialize than forEach with lambdas
        primitiveTypeToWrapperMap.put(boolean.class, Boolean.class);
        primitiveTypeToWrapperMap.put(byte.class, Byte.class);
        primitiveTypeToWrapperMap.put(char.class, Character.class);
        primitiveTypeToWrapperMap.put(double.class, Double.class);
        primitiveTypeToWrapperMap.put(float.class, Float.class);
        primitiveTypeToWrapperMap.put(int.class, Integer.class);
        primitiveTypeToWrapperMap.put(long.class, Long.class);
        primitiveTypeToWrapperMap.put(short.class, Short.class);
        primitiveTypeToWrapperMap.put(void.class, Void.class);
    }

    public abstract void transfer(Object var1, Object var2);

    public static Transition create(String name, Class targetType, ResultSetMetaData resultSetMetaData) {
        Generator gen = new Generator();
        gen.setTableName(name);
        gen.setTargetType(targetType);
        gen.setResultSetMetaData(resultSetMetaData);
        return gen.create();
    }

    public static class PlusClassEmitter extends ClassEmitter {
        private Map fieldMetaInfo;
        private static int metaHookCounter;

        // meta static hook
        private CodeEmitter metaStaticHook;
        private Signature metaStaticHookSig;

        // type mapping hook
        private CodeEmitter typeMappingHook;
        private Signature typeMappingHookSig;
        private String typeMappingFieldName;

        // get type mapping hook
        private CodeEmitter getTypeMappingHook;
        private Signature getTypeMappingHookSig;

        // target type hook
        private CodeEmitter targetTypeHook;
        private Signature targetTypeHookSig;
        private String propMappingFieldName;

        public PlusClassEmitter() {
            super();
        }

        public PlusClassEmitter(ClassVisitor cv) {
            super(cv);
            this.fieldMetaInfo = new HashMap();
            this.metaStaticHook = null;
            this.metaStaticHookSig = null;
            this.typeMappingHook = this.getTypeMappingHook = null;
            this.typeMappingHookSig = this.getTypeMappingHookSig = null;
        }

        private boolean isFieldMetaDeclared(String name) {
            return this.fieldMetaInfo.get(name) != null;
        }

        private static synchronized int getNextMetaHook() {
            return ++metaHookCounter;
        }

        private PlusClassEmitter.FieldMetaInfo getFieldMetaInfo(String name) {
            PlusClassEmitter.FieldMetaInfo fieldMeta = (PlusClassEmitter.FieldMetaInfo) this.fieldMetaInfo.get(name);
            if (fieldMeta == null) {
                throw new IllegalArgumentException("Field Meta " + name + " is not declared in " + this.getClassType().getClassName());
            } else {
                return fieldMeta;
            }
        }

        public void declare_meta_field(int access, String name, Type type, Object value) {
            PlusClassEmitter.FieldMetaInfo existing = (PlusClassEmitter.FieldMetaInfo) this.fieldMetaInfo.get(name);
            PlusClassEmitter.FieldMetaInfo metaInfo = new FieldMetaInfo(access, name, type, value);
            if (existing != null) {
                if (!metaInfo.equals(existing)) {
                    throw new IllegalArgumentException("Field Meta\"" + name + "\" has been declared differently");
                }
            } else {
                this.fieldMetaInfo.put(name, metaInfo);
                this.cv.visitField(access, name, type.getDescriptor(), (String) null, value);
            }
        }

        private CodeEmitter getMetaStaticHook() {
            if (TypeUtils.isInterface(this.getAccess())) {
                throw new IllegalStateException("meta static hook is invalid for this class");
            } else {
                if (this.metaStaticHook == null) {
                    this.metaStaticHookSig = new Signature("TRANSITION$METASTATICHOOK" + getNextMetaHook(), "()V");
                    this.metaStaticHook = this.begin_method(8, this.metaStaticHookSig, (Type[]) null);
                }

                return this.metaStaticHook;
            }
        }

        private String getClassMapName() {
            return "TRANSITION$type_mapping$" + TypeUtils.escapeType(Map.class.getName());
        }

        private String getPropertyMapName() {
            return "TRANSITION$property_mapping$" + TypeUtils.escapeType(Map.class.getName());
        }

        private CodeEmitter defineClassMap() {
            String mapName = getClassMapName();
            if (!this.isFieldMetaDeclared(mapName)) {
                this.typeMappingFieldName = mapName;
                this.declare_meta_field(Opcodes.ACC_PRIVATE |
                        Opcodes.ACC_STATIC |
                        Opcodes.ACC_FINAL, mapName, MAP, (Object) null);
                if (this.typeMappingHook == null) {
                    this.typeMappingHookSig = new Signature("TRANSITION$MAPHOOK" + this.getNextMetaHook(),
                            Type.VOID_TYPE, new Type[]{});
                    this.typeMappingHook = this.begin_method(8, this.typeMappingHookSig, (Type[]) null);
                    CodeEmitter t = this.typeMappingHook;
                    Generator.getfield(t, mapName);
                    Label put = t.make_label();
                    t.ifnonnull(put);
                    t.new_instance(HASH_MAP);
                    t.dup();
                    t.invoke_constructor(HASH_MAP);
                    Generator.putfield(t, mapName);
                    t.goTo(put);
                    t.mark(put);
                }

            }
            return this.typeMappingHook;
        }

        private void definePropMap(Class targetClazz) {
            String mapName = getPropertyMapName();
            if (!this.isFieldMetaDeclared(mapName)) {
                this.propMappingFieldName = mapName;
                this.declare_meta_field(Opcodes.ACC_PRIVATE |
                        Opcodes.ACC_STATIC |
                        Opcodes.ACC_FINAL, mapName, MAP, (Object) null);
                if (this.targetTypeHook == null) {
                    Objects.requireNonNull(targetClazz, () -> "Unknown error occurred, target type access null.");
                    Type targetType = Type.getType(targetClazz);
                    this.targetTypeHookSig = new Signature("TRANSITION$TARGETHOOK" + this.getNextMetaHook(),
                            Type.VOID_TYPE, new Type[]{});
                    this.targetTypeHook = this.begin_method(8, this.targetTypeHookSig, (Type[]) null);
                    CodeEmitter g = this.targetTypeHook;
                    g.push(targetType.getClassName());
                    g.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
                    g.invoke_static_this(GET_PROPERTY_DESC);
                    Generator.putfield(g, mapName);
                }
            }
        }

        public void end_class() {
            super.end_class();

            end_m(this.metaStaticHook);
            end_m(this.typeMappingHook);
            end_m(this.targetTypeHook);
        }

        private void end_m(CodeEmitter e) {
            if (e != null) {
                e.return_value();
                e.end_method();
            }
        }

        public static class FieldMetaInfo {
            int access;
            String name;
            Type type;
            Object value;

            public FieldMetaInfo(int access, String name, Type type, Object value) {
                this.access = access;
                this.name = name;
                this.type = type;
                this.value = value;
            }

            public boolean equals(Object o) {
                if (o == null) {
                    return false;
                } else if (!(o instanceof PlusClassEmitter.FieldMetaInfo)) {
                    return false;
                } else {
                    PlusClassEmitter.FieldMetaInfo other = (PlusClassEmitter.FieldMetaInfo) o;
                    if (this.access == other.access && this.name.equals(other.name) && this.type.equals(other.type)) {
                        if (this.value == null ^ other.value == null) {
                            return false;
                        } else {
                            return this.value == null || this.value.equals(other.value);
                        }
                    } else {
                        return false;
                    }
                }
            }

            public int hashCode() {
                return this.access ^ this.name.hashCode() ^ this.type.hashCode() ^ (this.value == null ? 0 : this.value.hashCode());
            }

        }

    }


    public static class Generator extends AbstractClassGenerator {

        private static final Source SOURCE = new Source(Transition.class.getCanonicalName());

        private Class<?> targetType;
        private String tableName;
        private ResultSetMetaData resultSetMetaData;
        private static Map<String, String> types;

        public Generator() {
            super(SOURCE);
            this.setNamingPolicy(TransitionNamingPolicy.INSTANCE);
        }

        static class WrapCodeEmitter {
            private CodeEmitter wrap;
            private Consumer<String> consu;

            public WrapCodeEmitter(CodeEmitter wrap) {
                this.consu = (nameType) -> new Generator.WrapInvokeClass() {
                    public void wrapInvoke(String typeClassName) {
                        Generator.invokeClassHook(typeClassName, wrap);
                    }
                }.wrapInvoke(nameType);
                this.wrap = wrap;
            }

            public void classStaticHookWrap(String name) {
                if (this.consu != null) {
                    this.consu.accept(name);
                }
            }
        }

        public void setTargetType(Class<?> targetType) {
            Objects.requireNonNull(targetType, () -> "target type do not allow null.");
            if (!Modifier.isPublic(targetType.getModifiers())) {
                this.setNamePrefix(targetType.getName());
            }

            this.targetType = targetType;
        }

        public void setTableName(String tableName) {
            this.tableName = tableName;
        }

        public void setResultSetMetaData(ResultSetMetaData resultSetMetaData) {
            this.resultSetMetaData = resultSetMetaData;
            loadResultSetMeta();
        }

        @Override
        protected ClassLoader getDefaultClassLoader() {
            return this.targetType.getClassLoader();
        }

        public Transition create() {
            Object key = Transition.KEY_FACTORY.newInstance(this.tableName, this.targetType.getName(), this.resultSetMetaData);
            return (Transition) super.create(key);
        }

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

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

        private void loadResultSetMeta() {
            if (this.resultSetMetaData == null) {
                throw new IllegalArgumentException("resultSetMetaData could not be null in Generator.");
            }
            if (types == null) {
                types = new HashMap<>();
            }
            try {
                for (int i = 1; i < this.resultSetMetaData.getColumnCount() + 1; i++) {
                    String columnClassName = this.resultSetMetaData.getColumnClassName(i);
                    String columnName = underlineTransferSmallHump(this.resultSetMetaData.getColumnName(i));
                    types.put(columnName, columnClassName);
                }
            } catch (SQLException e) {
                throw new IllegalStateException("unknown SQL Exception occurred: " + e.getMessage());
            }
        }

        private boolean isNotEmpty(Map<?, ?> map) {
            return map != null && !map.isEmpty();
        }

        @Override
        public void generateClass(ClassVisitor v) throws Exception {
            ClassEmitter ce = new PlusClassEmitter(v);
            ce.begin_class(52, 1, this.getClassName(), Transition.TRANSITION, (Type[]) null, "Transition.java");
            EmitUtils.null_constructor(ce);
            CodeEmitter e = ce.begin_method(1, Transition.TRANSFER, (Type[]) null);
            staticHook(e);

            Local newRList = e.make_local();
            Local rSet = e.make_local();
            e.load_arg(1);
            e.checkcast(RESULT_SET);
            e.store_local(rSet);

            e.load_arg(0);
            e.checkcast(LIST);
            e.store_local(newRList);

            Local rSetMeta = e.make_local();
            e.load_local(rSet);
            e.invoke_interface(RESULT_SET, FETCH_METADATA);
            e.store_local(rSetMeta);

            Transition.result_loop(this, rSet, rSetMeta, e, new Generator.TransferCallBack() {
                @Override
                public void doTransfer(CodeEmitter e, Local cursor) {

                    PlusClassEmitter pce = (PlusClassEmitter) ce;
                    e.load_this();
                    e.load_local(rSetMeta);
                    e.load_local(cursor);
                    e.invoke_interface(RESULT_SET_METADATA, FETCH_METADATA_CLASS);
                    e.invoke_virtual_this(pce.getTypeMappingHookSig);
                    Local $field$class = e.make_local();
                    e.store_local($field$class);

                    e.load_local(rSetMeta);
                    e.load_local(cursor);
                    e.invoke_interface(RESULT_SET_METADATA, FETCH_METADATA_COLUMN_NAME);
                    e.invoke_static_this(INVOKE_HUMP);
                    Local $field$name = e.make_local();
                    e.store_local($field$name);

                    Label nonProcess = e.make_label();

                    Local prop$ = e.make_local();
                    Generator.getfield(e, pce.getPropertyMapName());
                    e.load_local($field$name);
                    e.invoke_interface(MAP, GET);
                    e.checkcast(PROPERTY_DESCRIPTOR);
                    e.store_local(prop$);

                    e.load_local($field$class);
                    e.ifnull(nonProcess);
                    e.load_local(prop$);
                    e.ifnull(nonProcess);
                    e.load_this();
                    e.load_local($field$class);
                    e.load_local(prop$);
                    e.invoke_virtual_this(COMPATIBLE);
                    e.if_jump(Opcodes.IFEQ, nonProcess);

                    Transition.debugPrinter(e, new Load() {
                        @Override
                        public void pushOperandStack(CodeEmitter e) {
                            e.load_local($field$class);
                        }
                    });

                    Transition.debugPrinter(e, new Load() {
                        @Override
                        public void pushOperandStack(CodeEmitter e) {
                            e.load_local($field$name);
                        }
                    });

                    Transition.debugPrinter(e, new Load() {
                        @Override
                        public void pushOperandStack(CodeEmitter e) {
                            e.load_arg(1);
                            e.load_local(cursor);
                            e.invoke_interface(RESULT_SET, TypeUtils.parseSignature("Object getObject(int)"));
                        }
                    });

                    Transition.debugPrinter(e, new Load() {
                        @Override
                        public void pushOperandStack(CodeEmitter e) {
                            e.push("-----------------------");
                        }
                    });

                    e.goTo(nonProcess);
                    e.mark(nonProcess);
                }
            });

            ce.end_class();
        }

        private void staticHook(CodeEmitter e) {
            if (isNotEmpty(this.types)) {
                PlusClassEmitter pce = (PlusClassEmitter) e.getClassEmitter();
                putMap(e);
                pce.definePropMap(this.targetType);
                begin_static_this(pce);
                begin_get(pce);
            } else {
                throw new IllegalStateException("illegal result set meta types, it do not allow null or empty.");
            }
        }

        private void putMap(CodeEmitter e) {
            PlusClassEmitter pce = (PlusClassEmitter) e.getClassEmitter();
            WrapCodeEmitter wce = new WrapCodeEmitter(e);
            CodeEmitter mapHook = pce.defineClassMap();
            if (mapHook != null) {
                this.types.values().stream().distinct().forEach(type -> {
                    String fieldN = Generator.getFieldName(type);
                    Generator.getfield(mapHook, pce.typeMappingFieldName);
                    mapHook.push(type);
                    wce.classStaticHookWrap(type);
                    Generator.getfield(mapHook, fieldN);
                    mapHook.invoke_interface(MAP, PUT);
                    mapHook.pop();
                });
            }
        }

        private void begin_get(PlusClassEmitter pce) {
            if (pce.getTypeMappingHook == null) {
                pce.getTypeMappingHookSig = new Signature("TRANSITION$GETHOOK" + pce.getNextMetaHook(),
                        Constants.TYPE_CLASS, new Type[]{Constants.TYPE_STRING});
                pce.getTypeMappingHook = pce.begin_method(Opcodes.ACC_PRIVATE, pce.getTypeMappingHookSig, (Type[]) null);
                CodeEmitter getHook = pce.getTypeMappingHook;
                Generator.getfield(getHook, pce.typeMappingFieldName);
                getHook.load_arg(0);
                getHook.invoke_interface(MAP, GET);
                getHook.checkcast(Constants.TYPE_CLASS);
                pce.end_m(getHook);
            }
        }

        private void begin_static_this(PlusClassEmitter pce) {
            CodeEmitter staticHook = pce.begin_method(8, Constants.SIG_STATIC, (Type[]) null);
            staticHook.invoke_static_this(pce.metaStaticHookSig);
            staticHook.invoke_static_this(pce.typeMappingHookSig);
            staticHook.invoke_static_this(pce.targetTypeHookSig);
            pce.end_m(staticHook);
        }

        private void add(CodeEmitter emitter) {
            emitter.visitInsn(CodeEmitter.ADD);
        }

        private static void invokeClassHook(String name, CodeEmitter e) {
            Type type = TypeUtils.parseType(name);
            if (TypeUtils.isPrimitive(type)) {
                if (type == Type.VOID_TYPE) {
                    throw new IllegalArgumentException("result set could not return void type.");
                }

                e.getstatic(TypeUtils.getBoxedType(type), "TYPE", Constants.TYPE_CLASS);
            } else {
                load_rsf_class_helper(e, type);
            }
        }

        private static void load_rsf_class_helper(CodeEmitter e, Type type) {
            if (e.isStaticHook()) {
                e.push(TypeUtils.emulateClassGetName(type));
                e.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
            } else {
                PlusClassEmitter ce = (PlusClassEmitter) e.getClassEmitter();
                String typeName = TypeUtils.emulateClassGetName(type);
                String fieldName = Generator.getFieldName(typeName);
                if (!ce.isFieldMetaDeclared(fieldName)) {
                    ce.declare_meta_field(Opcodes.ACC_PRIVATE |
                            Opcodes.ACC_STATIC |
                            Opcodes.ACC_FINAL, fieldName, Constants.TYPE_CLASS, (Object) null);
                    CodeEmitter hook = ce.getMetaStaticHook();
                    hook.push(typeName);
                    hook.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
                    hook.putstatic(ce.getClassType(), fieldName, Constants.TYPE_CLASS);
                }

            }
        }

        private static String getFieldName(String typeName) {
            return "TRANSITION$load_meta_class$" + TypeUtils.escapeType(typeName);
        }

        private static void putfield(CodeEmitter e, String fieldName) {
            PlusClassEmitter ce = (PlusClassEmitter) e.getClassEmitter();
            PlusClassEmitter.FieldMetaInfo fieldMetaInfo = ce.getFieldMetaInfo(fieldName);
            Objects.requireNonNull(fieldMetaInfo, () -> "putfield fieldMetaInfo access null, unknown error.");
            int opcode = TypeUtils.isStatic(fieldMetaInfo.access) ? 179 : 181;
            switch (opcode) {
                case 179:
                    e.putstatic(ce.getClassType(), fieldMetaInfo.name, fieldMetaInfo.type);
                    break;
                case 181:
                    e.putfield(ce.getClassType(), fieldMetaInfo.name, fieldMetaInfo.type);
                    break;
                default:
                    throw new IllegalStateException("Unknown putfiled opcode access: " + opcode);
            }
        }

        private static void getfield(CodeEmitter e, String fieldName) {
            PlusClassEmitter ce = (PlusClassEmitter) e.getClassEmitter();
            PlusClassEmitter.FieldMetaInfo fieldMetaInfo = ce.getFieldMetaInfo(fieldName);
            Objects.requireNonNull(fieldMetaInfo, () -> "getfield fieldMetaInfo access null, unknown error.");
            int opcode = TypeUtils.isStatic(fieldMetaInfo.access) ? 178 : 180;
            switch (opcode) {
                case 178:
                    e.getstatic(ce.getClassType(), fieldMetaInfo.name, fieldMetaInfo.type);
                    break;
                case 180:
                    e.getfield(ce.getClassType(), fieldMetaInfo.name, fieldMetaInfo.type);
                    break;
                default:
                    throw new IllegalStateException("Unknown getfiled opcode access: " + opcode);
            }
        }

        private interface WrapInvokeClass {
            public void wrapInvoke(String typeClassName);
        }

        private interface TransferCallBack {
            void doTransfer(CodeEmitter e, Local cursor);
        }

        private interface ResultProcessCallBack {
            /**
             * @param e codeEmitter {@link org.springframework.cglib.core.CodeEmitter}
             */
            public void loop_around(CodeEmitter e, Generator.TransferCallBack callBack);

            /**
             * @param e codeEmitter {@link org.springframework.cglib.core.CodeEmitter}
             */
            void loop_end(CodeEmitter e);

            static void process_loop(ResultProcessCallBack callBack, CodeEmitter e, Generator.TransferCallBack c) {
                if (callBack == null)
                    throw new NullPointerException("ResultProcessCallBack null");

                callBack.loop_around(e, c);
                callBack.loop_end(e);
            }
        }
    }

    interface TransitionKey {
        Object newInstance(String var1, String var2, ResultSetMetaData resultSetMetaData);
    }

    static class TransitionNamingPolicy extends DefaultNamingPolicy {
        public static final TransitionNamingPolicy INSTANCE = new TransitionNamingPolicy();

        @Override
        protected String getTag() {
            return "ByXiaoxu";
        }
    }

    public static Map getPropertyDesc(Class type) {
        if (type == null)
            throw new NullPointerException("match type is null.");
        PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(type);
        Map names = new HashMap();

        for (int i = 0; i < setters.length; i++) {
            names.put(setters[i].getName(), setters[i]);
        }
        return names;
    }

    public boolean isCompatible(Class<?> clazz, PropertyDescriptor setter) {
        Class<?> propertyType = setter.getPropertyType();
        if (propertyType.isPrimitive()) {
            Class<?> wrapperType = primitiveTypeToWrapperMap.get(propertyType);
            return clazz.equals(wrapperType);
        }
        return propertyType.isAssignableFrom(clazz);
    }

    private static void result_loop(Transition.Generator generator, Local result$, Local resultMeta$, CodeEmitter e,
                                    Generator.TransferCallBack transferCallBack) {

        final Generator.ResultProcessCallBack loopCallBack = new Generator.ResultProcessCallBack() {

            @Override
            public void loop_around(CodeEmitter e, Generator.TransferCallBack callBack) {
                // forEach
                Label hasNext = e.make_label();
                e.mark(hasNext);

                e.load_local(result$);
                e.invoke_interface(RESULT_SET, NEXT);
                Label end = e.make_label();
                e.if_jump(Opcodes.IFEQ, end);
                e.push(1);

                Local cursor = e.make_local(Type.INT_TYPE);
                e.store_local(cursor);

                Label increment = e.make_label();
                e.mark(increment);

                callBack.doTransfer(e, cursor);

                e.iinc(cursor, 1);
                e.load_local(cursor);
                e.load_local(resultMeta$);
                e.invoke_interface(RESULT_SET_METADATA, FETCH_METADATA_COUNT);
                e.push(1);
                generator.add(e);
                e.if_icmp(Opcodes.IFLT, increment);
                e.goTo(hasNext);

                e.mark(end);
            }

            @Override
            public void loop_end(CodeEmitter e) {
                e.return_value();
                e.end_method();
            }
        };

        Generator.ResultProcessCallBack
                .process_loop(loopCallBack, e, transferCallBack);
    }

    /**
     * @param e      codeEmitter
     * @param loader operand stack action call back
     * @see System.out
     * @see java.io.PrintStream#println(String)
     */
    private static void debugPrinter(CodeEmitter e, Load loader) {
        e.getstatic(TypeUtils.parseType("System"), "out", TypeUtils.parseType("java.io.PrintStream"));
        loader.pushOperandStack(e);
        e.invoke_virtual(TypeUtils.parseType("java.io.PrintStream"), TypeUtils.parseSignature("void println(Object)"));
    }

    private interface Load {
        void pushOperandStack(CodeEmitter e);
    }

    /**
     * @param name 下划线
     * @return 小驼峰
     */
    public static String underlineTransferSmallHump(String name) {
        return symbolTransferSmallCamel(name, underLineMark.toCharArray()[0]);
    }

    public static boolean nonEmptyContains(String str1, String str2) {
        return str1.contains(str2);
    }

    @SuppressWarnings("all")
    public static String symbolTransferSmallCamel(String name, Character symbol) {
        if (null == symbol) {
            throw new RuntimeException("symbol access empty");
        }

        if (nonEmptyContains(name, symbol.toString())) {
            CharSequence cs = name;
            int i = 0, csLen = cs.length();
            StringBuilder sbd = new StringBuilder(csLen);
            boolean isUpper = false;

            for (; i < csLen; ++i) {
                char c;
                if (i == 0 && Character.isUpperCase(c = cs.charAt(i))) {
                    sbd.append(Character.toLowerCase(c));
                    continue;
                }

                c = cs.charAt(i);
                if (c == symbol) {

                    isUpper = true;

                } else if (isUpper) {

                    if (sbd.length() == 0) {
                        sbd.append(Character.toLowerCase(c));
                    } else {
                        sbd.append(Character.toUpperCase(c));
                    }
                    isUpper = false;
                } else {
                    sbd.append(c);
                }
            }

            return sbd.toString();
        } else {
            int strLen;
            return (strLen = name.length()) > 1
                    ? name.substring(0, 1).toLowerCase() + name.substring(1, strLen)
                    : name.toLowerCase();
        }
    }

}

执行准备的单测方法:

private DruidDataSource dataSource;

@Before
public void before() {
    dataSource = new DruidDataSource();
    dataSource.setInitialSize(2);
    dataSource.setMinIdle(0);
    dataSource.setMaxActive(4);

    dataSource.setMaxWait(5000);
    dataSource.setMinEvictableIdleTimeMillis(1000L * 60 * 5);
    dataSource.setMaxEvictableIdleTimeMillis(1000L * 60 * 120);

    dataSource.setUrl("jdbc:mysql://localhost:3306/xiaoxu?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC");
    dataSource.setUsername("root");
    dataSource.setPassword("******");
}

@Test
public void testUserQuery() throws Exception {
    try (Connection connect = this.dataSource.getConnection();) {
        try (PreparedStatement preparedStatement = connect.prepareStatement("select * from my_people");) {
            ResultSet resultSet = preparedStatement.executeQuery();

            System.getProperties().put("cglib.debugLocation", "src/test/java/trans_asm/printer");

            StopWatch stopWatch = new StopWatch("query time");
            stopWatch.start();
            Transition user = Transition.create("user", UserModel.class, resultSet.getMetaData());
            List<UserModel> userModelList = new ArrayList<>();
            user.transfer(userModelList, resultSet);
            stopWatch.stop();
            System.out.println("结束时间:" + stopWatch.getTotalTimeSeconds() + "s.");
        }
    }

}

UserModel如下(实体类中不能使用下划线,必须小驼峰定义字段):

@Data
@ToString
public class UserModel {
    String myName;
    int id;
    int age;
    BigDecimal moneyMe;
    LocalDateTime birthday;
}

表字段及内容如下:

在这里插入图片描述

但单测执行,我们希望把生成的字节码文件写入本地文件,非单测可以直接加上System.getProperties().put(“cglib.debugLocation”, “src/test/java/trans_asm/printer”),这里选择在idea上找到Edit Configurations,增加vm Options如下:-Dcglib.debugLocation=src/test/java/trans_asm/printer

在这里插入图片描述

问题1:查看生成的字节码文件,存在多个,说明未命中Spring LoadingCache中的map缓存,每次执行都是新生成的字节码文件:

在这里插入图片描述

问题2:观察idea展示的字节码文件,本质上还是循环在获取ResultSet的值,后续还是只能走反射调用,效率偏低,不符合高性能执行的要求:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.cglib.empty;

import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import trans.Transition;

public class Object$$TransitionByXiaoxu$$74604108 extends Transition {
    private static final Map TRANSITION$type_mapping$java$2Eutil$2EMap;
    private static final Class TRANSITION$load_meta_class$java$2Etime$2ELocalDateTime;
    private static final Class TRANSITION$load_meta_class$java$2Elang$2EString;
    private static final Class TRANSITION$load_meta_class$java$2Elang$2EInteger;
    private static final Map TRANSITION$property_mapping$java$2Eutil$2EMap;

    public Object$$TransitionByXiaoxu$$74604108() {
    }

    public void transfer(Object var1, Object var2) {
        ResultSet var4 = (ResultSet)var2;
        List var3 = (List)var1;
        ResultSetMetaData var5 = var4.getMetaData();

        while(var4.next()) {
            int var6 = 1;

            while(true) {
                Class var7 = this.TRANSITION$GETHOOK4(var5.getColumnClassName(var6));
                String var8 = underlineTransferSmallHump(var5.getColumnName(var6));
                PropertyDescriptor var9 = (PropertyDescriptor)TRANSITION$property_mapping$java$2Eutil$2EMap.get(var8);
                if (var7 != null && var9 != null && this.isCompatible(var7, var9)) {
                    System.out.println(var7);
                    System.out.println(var8);
                    System.out.println(((ResultSet)var2).getObject(var6));
                    System.out.println("-----------------------");
                }

                ++var6;
                if (var6 >= var5.getColumnCount() + 1) {
                    break;
                }
            }
        }

    }

    static void TRANSITION$MAPHOOK1() {
        if (TRANSITION$type_mapping$java$2Eutil$2EMap == null) {
            TRANSITION$type_mapping$java$2Eutil$2EMap = new HashMap();
        }

        TRANSITION$type_mapping$java$2Eutil$2EMap.put("java.time.LocalDateTime", TRANSITION$load_meta_class$java$2Etime$2ELocalDateTime);
        TRANSITION$type_mapping$java$2Eutil$2EMap.put("java.lang.String", TRANSITION$load_meta_class$java$2Elang$2EString);
        TRANSITION$type_mapping$java$2Eutil$2EMap.put("java.lang.Integer", TRANSITION$load_meta_class$java$2Elang$2EInteger);
    }

    static void TRANSITION$METASTATICHOOK2() {
        TRANSITION$load_meta_class$java$2Etime$2ELocalDateTime = Class.forName("java.time.LocalDateTime");
        TRANSITION$load_meta_class$java$2Elang$2EString = Class.forName("java.lang.String");
        TRANSITION$load_meta_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
    }

    static void TRANSITION$TARGETHOOK3() {
        TRANSITION$property_mapping$java$2Eutil$2EMap = getPropertyDesc(Class.forName("trans_asm.UserModel"));
    }

    static {
        CGLIB$STATICHOOK7();
        TRANSITION$METASTATICHOOK2();
        TRANSITION$MAPHOOK1();
        TRANSITION$TARGETHOOK3();
    }

    static void CGLIB$STATICHOOK7() {
    }

    private Class TRANSITION$GETHOOK4(String var1) {
        return (Class)TRANSITION$type_mapping$java$2Eutil$2EMap.get(var1);
    }
}

针对以上问题,每次生成的字节码文件不同,主要还是因为KEY_FACTORY.newInstance的各个value,其hashCode方法返回的值,存在不固定的对象,例如this.resultSetMetaData(该类并未重写hashCode方法,导致每次获取相同表信息不一致),导致从LoadingCache中的map获取时无法命中原本的值,另外将方式改造仿写为BeanCopier的方式,现改进如下:

新增meta元信息实体类(重点:同时重写hashCode和equals方法):

package trans;

import java.util.Arrays;
import java.util.Objects;

/**
 * @author xiaoxu
 * @date 2023-09-11
 * spring_boot:trans.FieldMetaSet
 */
public class FieldMetaSet {
    private FieldMeta[] fieldMetas;

    public FieldMeta[] getFieldMetas() {
        return fieldMetas;
    }

    public void setFieldMetas(FieldMeta[] fieldMetas) {
        this.fieldMetas = fieldMetas;
    }

    public int indexOf(FieldMeta fieldMeta) {
        Objects.requireNonNull(fieldMeta, () -> "index of's fieldMeta " + fieldMeta + "could not be null.");

        for (int i = 0; i < this.fieldMetas.length; i++) {
            if(this.fieldMetas[i].equals(fieldMeta)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        FieldMetaSet that = (FieldMetaSet) o;
        return Arrays.equals(fieldMetas, that.fieldMetas);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(fieldMetas);
    }
}

package trans;

import java.util.Objects;

/**
 * @author xiaoxu
 * @date 2023-09-11
 * spring_boot:trans.FieldMeta
 */
public class FieldMeta {
    String className;
    String columnName;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getColumnName() {
        return columnName;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        FieldMeta fieldMeta = (FieldMeta) o;
        return Objects.equals(className, fieldMeta.className) && Objects.equals(columnName, fieldMeta.columnName);
    }

    @Override
    public int hashCode() {
        // Objects.hash(className, columnName);
        return 111 + 3 * (className == null ? 0 : className.hashCode()) + 17 * (columnName == null ? 0 : columnName.hashCode());
    }
}

高性能TransitionBean工具类:

package trans;

import com.mysql.cj.jdbc.result.ResultSetMetaData;
import com.mysql.cj.result.Field;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Label;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.cglib.core.*;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.function.IntFunction;

/**
 * @author xiaoxu
 * @date 2023-09-11
 * spring_boot:trans.TransitionBean
 */
@SuppressWarnings("all")
public abstract class TransitionBean {

    private static final String underLineMark = "_";
    private static final boolean treatYearAsData = Boolean.parseBoolean(System.getProperty("treatYearToDate", "true"));
    private static final Map<String, Class<?>> typeMapping;

    private static final TransitionBeanKey KEY_FACTORY = (TransitionBeanKey) KeyFactory.create(TransitionBean.TransitionBeanKey.class);
    private static final Type TRANSITION_BEAN = TypeUtils.parseType("trans.TransitionBean");
    private static final Type RESULT_SET = TypeUtils.parseType("java.sql.ResultSet");
    private static final Type LIST = TypeUtils.parseType("java.util.List");

    private static final Signature TRANSITION;
    private static final Signature FETCH_METADATA;
    private static final Signature NEXT;
    private static final Signature GET_OBJECT;
    private static final Signature ADD_ELEMENT;

    private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new IdentityHashMap<>(9);

    public TransitionBean() {
    }

    static {
        typeMapping = new HashMap<>();
        TRANSITION = new Signature("transition", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT});
        FETCH_METADATA = TypeUtils.parseSignature("java.sql.ResultSetMetaData getMetaData()");
        NEXT = new Signature("next", Type.BOOLEAN_TYPE, new Type[0]);
        GET_OBJECT = TypeUtils.parseSignature("Object getObject(int)");
        ADD_ELEMENT = TypeUtils.parseSignature("boolean add(Object)");
        staticPrimHook();
    }

    private static void staticPrimHook() {
        // Map entry iteration is less expensive to initialize than forEach with lambdas
        primitiveTypeToWrapperMap.put(boolean.class, Boolean.class);
        primitiveTypeToWrapperMap.put(byte.class, Byte.class);
        primitiveTypeToWrapperMap.put(char.class, Character.class);
        primitiveTypeToWrapperMap.put(double.class, Double.class);
        primitiveTypeToWrapperMap.put(float.class, Float.class);
        primitiveTypeToWrapperMap.put(int.class, Integer.class);
        primitiveTypeToWrapperMap.put(long.class, Long.class);
        primitiveTypeToWrapperMap.put(short.class, Short.class);
        primitiveTypeToWrapperMap.put(void.class, Void.class);
    }

    public abstract void transition(Object var1, Object var2);

    public static TransitionBean create(Class targetEntityType, ResultSet result) {
        TransitionBean.Generator gen = new TransitionBean.Generator();
        gen.setTargetType(targetEntityType);
        gen.setFieldMetaSet(TransitionBean.convertFieldMetaSet(result));
        return gen.create();
    }

    public static class PlusClassEmitter extends ClassEmitter {
        private Map fieldMetaInfo;
        private static int metaHookCounter;

        public PlusClassEmitter() {
            super();
        }

        public PlusClassEmitter(ClassVisitor cv) {
            super(cv);
        }

        private boolean isFieldMetaDeclared(String name) {
            return this.fieldMetaInfo.get(name) != null;
        }

        private static synchronized int getNextMetaHook() {
            return ++metaHookCounter;
        }

        private PlusClassEmitter.FieldMetaInfo getFieldMetaInfo(String name) {
            PlusClassEmitter.FieldMetaInfo fieldMeta = (PlusClassEmitter.FieldMetaInfo) this.fieldMetaInfo.get(name);
            if (fieldMeta == null) {
                throw new IllegalArgumentException("Field Meta " + name + " is not declared in " + this.getClassType().getClassName());
            } else {
                return fieldMeta;
            }
        }

        public void declare_meta_field(int access, String name, Type type, Object value) {
            PlusClassEmitter.FieldMetaInfo existing = (PlusClassEmitter.FieldMetaInfo) this.fieldMetaInfo.get(name);
            PlusClassEmitter.FieldMetaInfo metaInfo = new PlusClassEmitter.FieldMetaInfo(access, name, type, value);
            if (existing != null) {
                if (!metaInfo.equals(existing)) {
                    throw new IllegalArgumentException("Field Meta\"" + name + "\" has been declared differently");
                }
            } else {
                this.fieldMetaInfo.put(name, metaInfo);
                this.cv.visitField(access, name, type.getDescriptor(), (String) null, value);
            }
        }

        public void end_class() {
            super.end_class();

        }

        private void end_m(CodeEmitter e) {
            if (e != null) {
                e.return_value();
                e.end_method();
            }
        }

        public static class FieldMetaInfo {
            int access;
            String name;
            Type type;
            Object value;

            public FieldMetaInfo(int access, String name, Type type, Object value) {
                this.access = access;
                this.name = name;
                this.type = type;
                this.value = value;
            }

            public boolean equals(Object o) {
                if (o == null) {
                    return false;
                } else if (!(o instanceof PlusClassEmitter.FieldMetaInfo)) {
                    return false;
                } else {
                    PlusClassEmitter.FieldMetaInfo other = (PlusClassEmitter.FieldMetaInfo) o;
                    if (this.access == other.access && this.name.equals(other.name) && this.type.equals(other.type)) {
                        if (this.value == null ^ other.value == null) {
                            return false;
                        } else {
                            return this.value == null || this.value.equals(other.value);
                        }
                    } else {
                        return false;
                    }
                }
            }

            public int hashCode() {
                return this.access ^ this.name.hashCode() ^ this.type.hashCode() ^ (this.value == null ? 0 : this.value.hashCode());
            }

        }

    }

    public static class Generator extends AbstractClassGenerator {

        private static final Source SOURCE = new Source(TransitionBean.class.getCanonicalName());

        private Class<?> targetType;
        private FieldMetaSet fieldMetaSet;
        private ClassLoader contextLoader;

        protected Generator() {
            super(SOURCE);
            this.setNamingPolicy(TransitionBeanNamingPolicy.INSTANCE);
            this.setNamePrefix(TransitionBean.class.getName());
            this.contextLoader = TransitionBean.class.getClassLoader();
        }

        public void setTargetType(Class<?> targetType) {
            Objects.requireNonNull(targetType, () -> "target type do not allow null.");
            if (!Modifier.isPublic(targetType.getModifiers())) {
                this.setNamePrefix(targetType.getName());
            }

            this.targetType = targetType;
        }

        public void setFieldMetaSet(FieldMetaSet fieldMetaSet) {
            Assert.notNull(fieldMetaSet, () -> "fieldMetaSet access null");
            Assert.notEmpty(fieldMetaSet.getFieldMetas(), () -> "fields meta should not be empty.");
            this.fieldMetaSet = fieldMetaSet;
        }

        public TransitionBean create() {
            Object key = TransitionBean.KEY_FACTORY.newInstance(this.targetType.getName(), this.fieldMetaSet);
            return (TransitionBean) super.create(key);
        }

        @Override
        protected ClassLoader getDefaultClassLoader() {
            return this.targetType.getClassLoader();
        }

        @Override
        protected Object firstInstance(Class type) throws Exception {
            return ReflectUtils.newInstance(type);
        }

        @Override
        protected Object nextInstance(Object instance) throws Exception {
            return instance;
        }

        private boolean nullSafeEquals(String var1, String var2) {
            // both null also is wrong
            return var1 != null && var1.equals(var2);
        }

        public boolean isCompatible(Class<?> clazz, PropertyDescriptor setter) {
            if (clazz.isPrimitive())
                throw new IllegalStateException(clazz.getCanonicalName() + " clazz is primitive type here, it is wrong.");
            Class<?> propertyType = setter.getPropertyType();
            if (propertyType.isPrimitive()) {
                Class<?> wrapperType = primitiveTypeToWrapperMap.get(propertyType);
                return clazz.equals(wrapperType);
            }
            return propertyType.isAssignableFrom(clazz);
        }

        @Override
        public void generateClass(ClassVisitor v) throws Exception {
            ClassEmitter ce = new TransitionBean.PlusClassEmitter(v);
            ce.begin_class(52, 1, this.getClassName(), TransitionBean.TRANSITION_BEAN, (Type[]) null, "TransitionBean.java");
            EmitUtils.null_constructor(ce);
            CodeEmitter e = ce.begin_method(1, TransitionBean.TRANSITION, (Type[]) null);

            Local rsList = e.make_local();
            Local rs = e.make_local();
            e.load_arg(1);
            e.checkcast(RESULT_SET);
            e.store_local(rs);

            e.load_arg(0);
            e.checkcast(LIST);
            e.store_local(rsList);

            TransitionBean.nonNull(e, rsList, "var1 could not be null.");
            TransitionBean.nonNull(e, rs, "var2 could not be null.");

            PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(this.targetType);

            Map names = new HashMap();

            for (int i = 0; i < setters.length; ++i) {
                names.put(setters[i].getName(), setters[i]);
            }

            // for loop is less expensive than forEach lambda
            FieldMeta[] fieldMetas = this.fieldMetaSet.getFieldMetas();

            TransitionBean.for_loop(this, e, new TransitionCallBack() {
                @Override
                public void tansitionTo(CodeEmitter e) {
                    Local instance = Generator.this.newInstance(e);

                    for (int i = 0; i < fieldMetas.length; i++) {
                        FieldMeta fieldMeta = fieldMetas[i];
                        PropertyDescriptor prop = (PropertyDescriptor) names.get(
                                TransitionBean.underlineTransferSmallHump(fieldMeta.getColumnName())
                        );
                        if (prop != null) {
                            Class typeClass = typeMapping.computeIfAbsent(fieldMeta.getClassName(), (name) -> {
                                try {
                                    return Class.forName(name, true, Generator.this.contextLoader);
                                } catch (ClassNotFoundException notFoundException) {
                                    throw new IllegalStateException("Class could not found:" + name);
                                }
                            });
                            if (Generator.this.isCompatible(typeClass, prop)) {
                                int index = Generator.this.fieldMetaSet.indexOf(fieldMeta);
                                if (index == -1) {
                                    throw new IllegalStateException("Out of index of field:" + fieldMeta);
                                }
                                MethodInfo write = ReflectUtils.getMethodInfo(prop.getWriteMethod());
                                Type setterType = write.getSignature().getArgumentTypes()[0];
                                e.load_local(instance);
                                e.load_local(rs);
                                e.push(++index);
                                e.invoke_interface(RESULT_SET, GET_OBJECT);
                                e.unbox_or_zero(setterType);
                                e.invoke(write);
                            }
                        }
                    }

                    e.load_local(rsList);
                    e.load_local(instance);
                    e.invoke_interface(LIST, ADD_ELEMENT);
                    e.pop();
                }
            }, rs);

        }

        private Local newInstance(CodeEmitter e) {
            Type type = Type.getType(Generator.this.targetType);
            e.new_instance(type);
            e.dup();
            e.invoke_constructor(type);

            Local local = e.make_local();
            e.store_local(local);
            return local;
        }

        private interface TransitionCallBack {
            void tansitionTo(CodeEmitter e);
        }

        private interface ResultProcessCallBack {
            /**
             * @param e              {@link org.springframework.cglib.core.CodeEmitter}
             * @param transitionCall
             */
            public void loop_around(CodeEmitter e, Generator.TransitionCallBack transitionCall);

            /**
             * @param e codeEmitter {@link org.springframework.cglib.core.CodeEmitter}
             */
            void loop_end(CodeEmitter e);

            static void process(ResultProcessCallBack processCallBack, CodeEmitter e, Generator.TransitionCallBack c) {
                if (processCallBack == null)
                    throw new NullPointerException("ResultProcessCallBack null");
                processCallBack.loop_around(e, c);
                processCallBack.loop_end(e);
            }
        }
    }

    private static void nonNull(CodeEmitter e, Local local, String errorMsg) {
        e.load_local(local);
        e.dup();
        Label end = e.make_label();
        e.ifnonnull(end);
        e.throw_exception(Type.getType(NullPointerException.class), errorMsg);
        e.goTo(end);
        e.mark(end);
    }

    static class TransitionBeanNamingPolicy extends DefaultNamingPolicy {
        public static final TransitionBeanNamingPolicy INSTANCE = new TransitionBeanNamingPolicy();

        @Override
        protected String getTag() {
            return "ByXiaoXu";
        }
    }

    interface TransitionBeanKey {
        Object newInstance(String var1, FieldMetaSet var2);
    }

    public static FieldMetaSet convertFieldMetaSet(ResultSet resultSetImpl) {
        Objects.requireNonNull(resultSetImpl, () -> "resultSetImpl is null.");

        try {
            // mysql-connector-java:8.0.26 support
            if (resultSetImpl.getMetaData() instanceof ResultSetMetaData) {
                ResultSetMetaData resultSetImplData = (ResultSetMetaData) resultSetImpl.getMetaData();
                Field[] fields = resultSetImplData.getFields();
                FieldMeta[] fieldMetas = Arrays.stream(fields).map(f -> {
                    FieldMeta fieldMeta = new FieldMeta();
                    String originalName = f.getOriginalName();
                    fieldMeta.setColumnName(originalName == null ? f.getName() : originalName);

                    String className;
                    switch (f.getMysqlType()) {
                        case YEAR:
                            if (!treatYearAsData) {
                                className = Short.class.getName();
                                break;
                            }
                            className = f.getMysqlType().getClassName();
                            break;
                        default:
                            className = f.getMysqlType().getClassName();
                            break;
                    }
                    fieldMeta.setClassName(className);
                    return fieldMeta;
                }).toArray(new IntFunction<FieldMeta[]>() {
                    @Override
                    public FieldMeta[] apply(int value) {
                        return new FieldMeta[value];
                    }
                });

                FieldMetaSet fieldMetaSet = new FieldMetaSet();
                fieldMetaSet.setFieldMetas(fieldMetas);
                return fieldMetaSet;
            }

            throw new IllegalStateException("could not access fieldMetaSet.");
        } catch (SQLException sqlError) {
            throw new IllegalStateException(sqlError);
        }
    }

    private static void for_loop(TransitionBean.Generator generator, CodeEmitter e, Generator.TransitionCallBack c, Local result$) {
        final Generator.ResultProcessCallBack processor = new Generator.ResultProcessCallBack() {
            @Override
            public void loop_around(CodeEmitter e, Generator.TransitionCallBack transitionCall) {
                // forEach
                Label hasNext = e.make_label();
                e.mark(hasNext);

                e.load_local(result$);
                e.invoke_interface(RESULT_SET, NEXT);
                Label end = e.make_label();
                e.if_jump(Opcodes.IFEQ, end);

                transitionCall.tansitionTo(e);

                e.goTo(hasNext);
                e.mark(end);
            }

            @Override
            public void loop_end(CodeEmitter e) {
                e.return_value();
                e.end_method();
            }
        };

        Generator.ResultProcessCallBack.process(processor, e, c);
    }

    /**
     * @param e      codeEmitter
     * @param loader operand stack action call back
     * @see System.out
     * @see java.io.PrintStream#println(String)
     */
    private static void debugPrinter(CodeEmitter e, Load loader) {
        e.getstatic(TypeUtils.parseType("System"), "out", TypeUtils.parseType("java.io.PrintStream"));
        loader.pushOperandStack(e);
        e.invoke_virtual(TypeUtils.parseType("java.io.PrintStream"), TypeUtils.parseSignature("void println(Object)"));
    }

    private interface Load {
        void pushOperandStack(CodeEmitter e);
    }

    /**
     * @param name 下划线
     * @return 小驼峰
     */
    public static String underlineTransferSmallHump(String name) {
        return symbolTransferSmallCamel(name, underLineMark.toCharArray()[0]);
    }

    public static boolean nonEmptyContains(String str1, String str2) {
        return str1.contains(str2);
    }

    @SuppressWarnings("all")
    public static String symbolTransferSmallCamel(String name, Character symbol) {
        if (null == symbol) {
            throw new RuntimeException("symbol access empty");
        }

        if (nonEmptyContains(name, symbol.toString())) {
            CharSequence cs = name;
            int i = 0, csLen = cs.length();
            StringBuilder sbd = new StringBuilder(csLen);
            boolean isUpper = false;

            for (; i < csLen; ++i) {
                char c;
                if (i == 0 && Character.isUpperCase(c = cs.charAt(i))) {
                    sbd.append(Character.toLowerCase(c));
                    continue;
                }

                c = cs.charAt(i);
                if (c == symbol) {

                    isUpper = true;

                } else if (isUpper) {

                    if (sbd.length() == 0) {
                        sbd.append(Character.toLowerCase(c));
                    } else {
                        sbd.append(Character.toUpperCase(c));
                    }
                    isUpper = false;
                } else {
                    sbd.append(c);
                }
            }

            return sbd.toString();
        } else {
            int strLen;
            return (strLen = name.length()) > 1
                    ? name.substring(0, 1).toLowerCase() + name.substring(1, strLen)
                    : name.toLowerCase();
        }
    }
}

准备TransitionBean的单测方法:

@Test
public void testTransitionBean() throws Exception {
    try (Connection connect = this.dataSource.getConnection();) {
        try (PreparedStatement preparedStatement = connect.prepareStatement("select * from my_people");) {
            ResultSet resultSet = preparedStatement.executeQuery();

            System.getProperties().put("cglib.debugLocation", "src/test/java/trans_asm/printer");

            StopWatch stopWatch = new StopWatch("query time");
            stopWatch.start();
            TransitionBean transitionBean = TransitionBean.create(UserModel.class, resultSet);
            List<UserModel> userModelList = new ArrayList<>();
            transitionBean.transition(userModelList, resultSet);
            stopWatch.stop();
            System.out.println("结束时间:" + stopWatch.getTotalTimeMillis() + "ms.");
            userModelList.forEach(System.out::println);
        }
    }


    try (Connection connect = this.dataSource.getConnection();) {
        try (PreparedStatement preparedStatement = connect.prepareStatement("select * from my_people");) {
            ResultSet resultSet = preparedStatement.executeQuery();

            System.getProperties().put("cglib.debugLocation", "src/test/java/trans_asm/printer");

            StopWatch stopWatch = new StopWatch("query time");
            stopWatch.start();
            TransitionBean transitionBean = TransitionBean.create(UserModel.class, resultSet);
            List<UserModel> userModelList = new ArrayList<>();
            transitionBean.transition(userModelList, resultSet);
            stopWatch.stop();
            System.out.println("结束时间:" + stopWatch.getTotalTimeMillis() + "ms.");
            userModelList.forEach(System.out::println);
        }
    }
    
}

重复的表元信息等,字节码类仅生成一次:

在这里插入图片描述

字节码文件如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package trans;

import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.List;
import trans_asm.UserModel;

public class TransitionBean$$TransitionBeanByXiaoXu$$85653863 extends TransitionBean {
    public TransitionBean$$TransitionBeanByXiaoXu$$85653863() {
    }

    public void transition(Object var1, Object var2) {
        ResultSet var4 = (ResultSet)var2;
        List var3 = (List)var1;
        if (var3 == null) {
            throw new NullPointerException("var1 could not be null.");
        } else if (var4 == null) {
            throw new NullPointerException("var2 could not be null.");
        } else {
            while(var4.next()) {
                UserModel var5 = new UserModel();
                Object var10003 = var4.getObject(1);
                var5.setId(var10003 == null ? 0 : ((Number)var10003).intValue());
                var5.setMyName((String)var4.getObject(2));
                var5.setBirthday((LocalDateTime)var4.getObject(4));
                var3.add(var5);
            }

        }
    }
}

执行结果如下(可见第二次直接从map缓存中取出字节码类后并执行,执行时间大大减少,性能提升显著),且测试结果和反射的ResultSet工具类进行比对,其性能远超反射的实现方式:

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值