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