2021SC@SDUSC
Codegen初认识引用ActiveJ网站对于Codegen的介绍:
Codegen是基于ObjectWeb ASM的低开销的java代码生成器,是构成ActiveJ Serializer成为世界上最快的JVM序列化器的技术”。
看到这句话着实被这口气吓了一跳,但是在分析为什么Codegen这么厉害之前,对于我本人来说,先要递归的学习一下什么是ASM。
ASM这里要明白,ActiveJ是一个网络后端服务器,因此Codegen的串行化技术主要应用领域亦是在不同的计算机上远程传输数据。所以查询和理解的方向就是网络传输领域。
所以,ASM是什么呢?
————简单来讲,java的Class文件是通过javac编译器产生的,通过类加载器加载到虚拟机内,而ASM的功能,就是定义一系列API来直接生成符合java虚拟机规范的Class字节流,因此,ASM做的工作相当于就是javac做的工作,但是ASM生成的Class字节流更小更快。
结合ActiveJ的RPC(远程过程调用,Remote Procedure Call)功能,当一台服务器远程调用另一台服务器提供的方法时,由于底层网络采用TCP协议以二进制传输数据,因此服务器需要使用串行化技术(Serializer)来将参数串行化后发送给被调用的服务器,同时,结果的返回也需要以序列化后的二进制传输。所以,ASM(或者说,这里可以替换为ActiveJ-Codegen)的工作,就是生成更快更小的序列化数据,从而加快服务器相应速度。
因此,Codegen的功能,就是对于Class字节流的生成操作,其对于一个类的生成,经常使用上学期的反射和内省技术,结合官网的例子来认识第一眼Codegen:
public static void main(String[] args) throws ReflectiveOperationException {
//[START REGION_1]
Class greeterClass = ClassBuilder.create(Greeter.class)
.withMethod("sayHello",
call(staticField(System.class, "out"),
"println",
value("Hello world")))
.defineClass(CLASS_LOADER);
//[END REGION_1]
//[START REGION_2]
Greeter greeter = greeterClass.getDeclaredConstructor().newInstance();
greeter.sayHello();
//[END REGION_2]
}
这里使用面向对象课程里学到的元类(meta class)来定义了一个类,生成了一个类(注意不是定义),并执行了其sayHello()方法来产生输出。
并且官网提到了这个类生成语句中特别的一行:
call(staticField(System.class, "out"), "println", value("Hello world"))
Notice the call(staticField(System.class, "out"), "println", value("Hello world")) expression.
This is how expressions DSL looks like
意思是这里使用了ActiveJ的特别的一个expressions DSL,操作了system类的out域,并且调用了out的println()方法,将参数 "Hello world" 打印了出来。在官网给出的另一个例子中,也是使用了类似的expressions DSL语句。
为什么要特别地将expressions这个概念着重指出呢?
Codegen expression在Codegen部分的代码中,expression包作为最重要的部分,提供了使用expression来生成一个java Class的大部分功能。
首先是对于expression的定义,很容易想到,其应该是一个接口。
public interface expression {
Type load(Context ctx);
}
expression只有一个返回值类型为Type的load()方法,而关于Type,其被包含于org.objectweb.asm的package里,定义为 "A Java field or method type. This class can be used to make it easier to manipulate type and method descriptors." 这个解释理解起来有些困难,但是在官网给出的另一个例子中,即使用Codegen来构造动态类(dynamic class)里,使用了load()方法,这里暂时将这个疑问保留,之后再回答。
概览一遍expression包,可以发现,这些包都是以expression开头的包,然后后缀都是一些基本的功能,例如ArrayGet,即为列表提取成员;Cmp,即为比较二者是否相同。
为了有助于分析,这里首先打开一个expressionToString类来作为例子,因为toString()方法是我们所熟悉的。
expressionToString首先是这个类的注释:
public final class expressionToString implements expression {
}
解释为定义一些方法来生成一个字符串,和我们理解的toString()方法的效果初步看来是一致的。
之后是expressionToString类的成员变量:
private String begin = "{";
private String end = "}";
private String separator = " ";
private final Map
包含了一个开始符 "{" 和结束符 "}" 以及分隔符 " " ,应该是打印出一种格式为"{ X X X }"的字符串来,此外还有一个final变量Map
之后来分别查看定义的方法。
expressionToString() {
}
public static expressionToString create() {
return new expressionToString();
}
public expressionToString with(String label, expression expression) {
this.arguments.put(label, expression);
return this;
}
public expressionToString with(expression expression) {
this.arguments.put(arguments.size() + 1, expression);
return this;
}
- 构造函数是一个空的构造函数,但是为什么还要写呢,因为这个类是public,其默认构造函数的访问修饰符将为public,而这里写了构造函数,则将访问修饰符设置为了默认的default,这也是为什么提供了create()方法的原因。
- 一个静态方法create()提供外部调用,来生成一个expressionToString对象。
- 两个with方法,用来将参数的expression添加到定义的成员变量argument里,这里可以看到其Key的类型其实都是String。
public expressionToString withSeparator(String separator) {
this.separator = separator;
return this;
}
public expressionToString withQuotes(String begin, String end) {
this.begin = begin;
this.end = end;
return this;
}
- 一个是定义分隔符的方法,但是使用空格作为分隔符是常识,所以这个方法并没用被任何类调用...
- 一个是引用方法,即修改默认的begin和end符,为了某些特殊情况,不过也没有被调用...
最后就是最重要的load()方法的分析了
GeneratorAdapter g = ctx.getGeneratorAdapter();
g.newInstance(getType(StringBuilder.class));
g.dup();
g.invokeConstructor(getType(StringBuilder.class), getMethod("void ()"));
boolean first = true;
- 首先,expressionToString类也是实现了expression接口的一个类,所以需要重载load()方法根据传入参数Context ctx来生成对应的类生成器(Class builder),然后使用反射内省来生成一个StringBuilder类,并调用其init()初始化方法。
for (Map.Entry
- 之后,进入对Map argument的遍历中,遍历就变得简单一点。首先如果是第一个Map元素的话,就加begin字符串"{"加到首位,然后对元素的Key进行判断,如果是服合规范的字符串类型,则类生成器会分配一个位置,并且将为这个元素执行load(Context ctx)方法,即将传入的描述数据(Context 参数)添加到类生成器里,最后添加end字符串 "}" 。
- 如果遇到是空的类型,也会自动执行默认的null字符串生成,保证容错率。
return getType(String.class);
- 当遍历所有的argument元素后,这些元素的字符串打印就都被类生成器记录下来,作为将来的串行化时来代替这些类的toString()方法,生成特定格式的字符串来。
本次代码分析主要是从大体上分析Codegen部分的结构,然后深入到expression包里进行分析,最后以一个expressionToString类来作为例子分析,柿子要挑软的捏嘛,先分析这个比较简单和基础的类,认识到Codegen的expression的一个小功能,从而为以后理解Codegen的ASM功能打下基础。



