Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。
Javassist(JAVA编程ASSISTant)使Java字节码操作变得简单。 它是一个用Java编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载时修改类文件。 与其他类似的字节码编辑器不同,Javassist提供两个级别的API:源级别和字节码级别。 如果用户使用源级API,他们可以在不知道Java字节码规范的情况下编辑类文件。 整个API仅使用Java语言的词汇表进行设计。 您甚至可以以源文本的形式指定插入的字节码; Javassist即时编译它。 另一方面,字节码级API允许用户直接编辑类文件作为其他编辑器。
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
package com.ygz.demo.javassist;
import javassist.*;
/**
* @author jingchuan
*/
public class Test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建一个空的类
CtClass cc = pool.makeClass("com.ygz.demo.javassist.JavassistObj1");
// 新增一个字段名为name String 类型
CtField name = new CtField(pool.getCtClass("java.lang.String"), "name", cc);
// 访问级别是 private
name.setModifiers(Modifier.PRIVATE);
// 初始值是 "jingchuan"
cc.addField(name, CtField.Initializer.constant("jingchuan"));
// 新增一个字段名为id int 类型
CtField id = new CtField(pool.getCtClass("int"), "id", cc);
// 访问级别是 private
id.setModifiers(Modifier.PRIVATE);
// 初始值是 1
cc.addField(id, CtField.Initializer.constant(1));
// 给name字段添加 get、set 方法
cc.addMethod(CtNewMethod.setter("setName", name));
cc.addMethod(CtNewMethod.getter("getName", name));
// 给id字段添加 get、set 方法
cc.addMethod(CtNewMethod.setter("setId", id));
cc.addMethod(CtNewMethod.getter("getId", id));
// 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{
}, cc);
cons.setBody("{name = \"jingchuanya\"; id = 1;}");
cc.addConstructor(cons);
// 添加有参的构造函数
cons = new CtConstructor(new CtClass[]{
pool.get("int"), pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.id = $1;$0.name = $2;}");
cc.addConstructor(cons);
// 6. 创建一个名为printToString方法,无参数,无返回值,输出id和name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printToString", new CtClass[]{
}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);System.out.println(id);}");
cc.addMethod(ctMethod);
//todo 这里改成自己的路径
cc.writeFile("/Users/****/jingchuanSpace/xxx-demo/demo/src/main/java/");
Class<?> aClass = cc.toClass();
Object o = aClass.newInstance();
o.getClass().getMethod("printToString", new Class[]{
}).invoke(o);
}
}
执行这段代码会在自己填写的绝对路径下生成一个JavassistObj1.class文件
然后还会在打印台输出:
jingchuanya
1
这就是动态生成class文件并通过反射调用其中的方法的例子,javassist不止可以创建类,还可以对已有的类进行修改,这些这里就不细说了,大家可以自行去网上查阅资料,比如这个:https://www.cnblogs.com/scy251147/p/11100961.html
dubbo中的动态编译有一个体现的地方就是对于标注了@Adaptive
的方法,会自动生成一个代理类,在我上一篇分析SPI的时候提到了这里,感兴趣可以先看看SPI的知识:dubbo的SPI
那么这里就直接进入代码分析:org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass
首先是获取org.apache.dubbo.common.compiler.Compiler
,看过上一篇SPI的应该很熟悉这个代码
可以看到org.apache.dubbo.common.compiler.Compiler
注解了@SPI("javassist")
,所以其默认实现应该是org.apache.dubbo.common.compiler.support.JavassistCompiler
那么再回到org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass
方法中,获取到Compiler
的实现之后,调用了org.apache.dubbo.common.compiler.Compiler#compile
方法那么到底调用的是这个接口的哪个实现呢,org.apache.dubbo.common.compiler.support.AdaptiveCompiler
实现上面标记有注解:@Adaptive
,所以很明显是
因为他是通过:org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
获取出来的,那么来看实现方法:org.apache.dubbo.common.compiler.support.AdaptiveCompiler#compile
,在这个方法中有个org.apache.dubbo.common.compiler.support.AdaptiveCompiler#DEFAULT_COMPILER
变量,如果没有给他setDefaultCompiler
,那么他就是空的,所以会直接走到上图红框圈起来的方法loader.getDefaultExtension()
,那么这个loader
其实就是Compiler
的扩展类,所以他的默认实现就是@SPI("javassist")
的org.apache.dubbo.common.compiler.support.JavassistCompiler
,那么当我们想点进去compiler.compile(code, classLoader)
这个方法的时候,会发现这个接口实现的是一个抽象类org.apache.dubbo.common.compiler.support.AbstractCompiler
这个抽象类其实是被org.apache.dubbo.common.compiler.support.JavassistCompiler
继承了的
所以自然就进入了org.apache.dubbo.common.compiler.support.AbstractCompiler#compile
这个方法,那么这个方法的try catch之前实际是获取了一个className
,表示从入参code里面获取的类包路径+类名称,然后Class.forName
是加载编译后的Class,但是这个code才是刚生成的代码,还没有被编译过,所以自然就抛了异常,那么就进入了doCompile(className, code)
里面,这个时候就真正进入了org.apache.dubbo.common.compiler.support.JavassistCompiler#doCompile
那么来看这个doCompile
方法,看过文章第二小节的同学或者对javssist有了解的同学,应该能看懂这个方法的意思就是根据入参code生成了一个对应的Class并返回
那到这里基本就看完了dubbo使用javassist进行动态编译的过程