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

news2024/11/6 7:20:48

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工具类进行比对,其性能远超反射的实现方式:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1024996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试官:你了解Axios的原理吗?有看过它的源码吗?

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 一、axios的使用 二、实现一个简易版axios 三、源码分析 小结 一、axios的使用 关于axios的基本使用&#xff0…

暨南大学旅游管理《乡村振兴战略下传统村落文化旅游设计》校友许少辉—2023学生开学季辉少许

暨南大学旅游管理《乡村振兴战略下传统村落文化旅游设计》校友许少辉——2023学生开学季辉少许

软文发布是推动企业发展的无形动力 | 媒介启航

同样是销售产品&#xff0c;为什么以故事的形式呈现要比直接讲产品功效更易让消费者认同和销售呢&#xff1f;这就是软文的魅力&#xff0c;它极好的运用了人作为情感动物的喜好。因此&#xff0c;各大小企业纷纷做软文&#xff0c;比如溯源啊、初心啊、情怀啊、创业经历啊等等…

Java集成微信支付实现企业付款到零钱和商家转账到零钱的功能

Java集成微信支付实现企业付款到零钱和商家转账到零钱的功能 文章目录 [toc] 1.企业付款到零钱和商家转账到零钱的区别1.1 申请要求不同1.2 API接口不同1.3 用户收款限制1.4 商户付款额度1.5 派发方式不同1.6 打款方式不同 2.集成实现2.1 v2版本集成2.2 依赖2.3 配置2.3.1 naco…

java多线程学习笔记一

一、线程的概述 1.1 线程的相关概念 1.1.1 进程&#xff08;Process&#xff09; 进程&#xff08;Process&#xff09;是计算机的程序关于某数据集合上的一次运行活动&#xff0c;是操作系统进行资源分配与调度的基本单位。 可以把进程简单的理解为操作系统中正在有运行的一…

插入排序代码及时间空间复杂度

插入排序&#xff08;Insertion Sort&#xff09;是一种简单的排序算法&#xff0c;它将一个数组分成已排序和未排序两部分&#xff0c;然后逐步将未排序部分的元素插入已排序部分的正确位置。以下是插入排序的代码示例以及时间和空间复杂度分析&#xff0c;希望对大家有所帮助…

Markdown 文档标题序号重排版(WebUI Tool)

Markdown 标题重编号 1. 项目背景 在日常写 Markdown 时&#xff0c;我们可能会遇到这样的情况&#xff1a; 文档的迁移与整合&#xff1a;在迁移或整合文档时&#xff0c;可能会让原本的标题编号混乱文档的重构&#xff1a;在重构文档时&#xff0c;例如仅仅修改了一处标题&…

malloc与free

目录 前提须知&#xff1a; malloc&#xff1a; 大意&#xff1a; 头文件&#xff1a; 申请空间&#xff1a; 判断是否申请成功&#xff1a; 使用空间&#xff1a; 结果&#xff1a; 整体代码&#xff1a; malloc申请的空间怎么回收呢? 注意事项&#xff1a; free:…

uniapp级联菜单地点区域使用label值,web端el-cascader绑定的value

效果图 一、uniapp uniapp级联菜单地点区域使用label值 1.ui使用 <uni-forms-item label="地址" name="userArea" required><view class="" style="height: 100%;display: flex;align-items: center;">

神经网络 03(参数初始化)

一、参数初始化 对于某一个神经元来说&#xff0c;需要初始化的参数有两类&#xff1a;一类是权重W&#xff0c;还有一类是偏置b&#xff0c;偏置b初始化为0即可。而权重W的初始化比较重要&#xff0c;我们着重来介绍常见的初始化方式。 &#xff08;1&#xff09;随机初始化 …

zabbix监控告警邮箱提醒,钉钉提醒

一、注册网易邮箱及其配置邮箱 1、开启POP3/SMTP/IMAP 二、service端配置邮件服务 1.安装 mailx dos2unix yum install -y mailx dos2unix mailx&#xff1a;邮件服务 mos2unix&#xff1a;用于转换文本文件格式的实用工具 查看mailx版本 2.配置mailx配置文件 编辑&#xf…

控制理论::带零点的二阶系统时域响应分析

一、系统描述 二、系统分析(分类讨论分析) 1、类型一&#xff08;极点为实数&#xff08;阻尼比>1&#xff09;&#xff0c;零点为负实数&#xff08;τ-1/b<0&#xff09;&#xff09; 1.1 定性分析 1.2 定量分析 有零点二阶系统的动态性能分析 - 豆丁网 (docin.com)

Python WEB框架FastAPI (二)

Python WEB框架FastAPI &#xff08;二&#xff09; 最近一直在使用fastapi&#xff0c;随着使用的深入发现我对于它的了解还是太少了&#xff0c;以至于踩了一些坑。所以在这里记录一下&#xff0c;愿看到的小伙伴不迷路。 路径传参并发问题 一、路径传参 这是对上一个传参…

TTS | 利用Fastspeech训练LJSpeech语音数据集后英文文本生成语音及代码详解

FastSpeech 基于 Transformer 的前馈网络&#xff0c;用于并行生成 TTS 梅尔谱图。 FastSpeech 模型与自回归 Transformer TTS 相比&#xff0c;梅尔谱图生成速度加快了 270 倍&#xff0c;端到端语音合成速度加快了 38 倍。 项目实现 docker cp LJSpeech-1.1.tar.bz2 torch_…

Kibana 安装部署 - Centos7

Kibana 安装部署 - Centos7 本文介绍一下在Centos7上部署和配置Kibana1、下载 直接去官网地址进行下载即可。【注意】&#xff1a; 一定要下载和你的ES版本一致的Kibana。比如我的ES的版本是 7.9.1&#xff0c;所以我下载的kibana就是7.9.1。下载地址 &#xff1a; https://w…

活动报名|如何使用70万预算从头训练千亿语言大模型

王业全 北京智源人工智能研究院认知模型团队负责人&#xff0c;清华大学博士&#xff0c;中国中文信息学会情感计算专委会委员&#xff0c;2022年被评为AI 2000全球最具影响力人工智能学者&#xff08;自然语言处理领域&#xff09;。主要从事语言大模型、自然语言处理方面的研…

Laravel框架 - 中间件篇

什么是中间件&#xff1f; 在 Laravel 框架中&#xff0c;中间件是一种用于处理 HTTP 请求的组件。它允许你在请求进入 路由 处理 之前 或 之后 执行一些代码逻辑。 中间件的优势和功能 处理身份验证&#xff1a;验证用户是否已经登录或者检查用户是否有权限访问特定的路由 记…

国外发达国家码农是真混得好么?

来看看花旗工作十多年的码农怎么说吧! 美国最大的论坛 Reddit&#xff0c;之前有一个热帖&#xff1a; 一个程序员说自己喝醉了&#xff0c;软件工程师已经当了10年&#xff0c;心里有 好多话想说&#xff0c;“我可能会后悔今天说了这些话。”他洋洋洒洒写了 一大堆&#xff…

Knife4jInsight ,Knife4j 的商业化产品之路

Knife4jInsight &#xff0c;Knife4j 的商业化产品之路 写在前面产品定位产品名称技术架构功能架构产品定价最后 Knife4jInsight &#xff0c;Knife4j 的商业化产品之路 在之前发布的《Knife4j新产品的想法》一文中&#xff0c;我提到想给Knife4j的生态做一些扩展&#xff0c…

使用Python和XPath解析动态JSON数据

JSON动态数据在Python中扮演着重要的角色&#xff0c;为开发者提供了处理实时和灵活数据的能力。Python作为一种强大的编程语言&#xff0c;提供了丰富的工具和库来处理动态JSON数据使得解析和处理动态JSON数据变得简单和高效。例如&#xff0c;使用内置的json模块&#xff0c;…