dubbo的动态编译javassist

一. javassist简介

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。

Javassist(JAVA编程ASSISTant)使Java字节码操作变得简单。 它是一个用Java编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载时修改类文件。 与其他类似的字节码编辑器不同,Javassist提供两个级别的API:源级别和字节码级别。 如果用户使用源级API,他们可以在不知道Java字节码规范的情况下编辑类文件。 整个API仅使用Java语言的词汇表进行设计。 您甚至可以以源文本的形式指定插入的字节码; Javassist即时编译它。 另一方面,字节码级API允许用户直接编辑类文件作为其他编辑器。

二. javassist使用

  • 引入依赖
<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文件dubbo的动态编译javassist_第1张图片
然后还会在打印台输出:

jingchuanya
1

这就是动态生成class文件并通过反射调用其中的方法的例子,javassist不止可以创建类,还可以对已有的类进行修改,这些这里就不细说了,大家可以自行去网上查阅资料,比如这个:https://www.cnblogs.com/scy251147/p/11100961.html

三. dubbo使用javassist动态编译

dubbo中的动态编译有一个体现的地方就是对于标注了@Adaptive的方法,会自动生成一个代理类,在我上一篇分析SPI的时候提到了这里,感兴趣可以先看看SPI的知识:dubbo的SPI
那么这里就直接进入代码分析:org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass
dubbo的动态编译javassist_第2张图片
首先是获取org.apache.dubbo.common.compiler.Compiler,看过上一篇SPI的应该很熟悉这个代码dubbo的动态编译javassist_第3张图片
可以看到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,所以很明显是dubbo的动态编译javassist_第4张图片
因为他是通过: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
dubbo的动态编译javassist_第5张图片
这个抽象类其实是被org.apache.dubbo.common.compiler.support.JavassistCompiler继承了的dubbo的动态编译javassist_第6张图片
所以自然就进入了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#doCompiledubbo的动态编译javassist_第7张图片
那么来看这个doCompile方法,看过文章第二小节的同学或者对javssist有了解的同学,应该能看懂这个方法的意思就是根据入参code生成了一个对应的Class并返回

那到这里基本就看完了dubbo使用javassist进行动态编译的过程

你可能感兴趣的:(死磕dubbo,javassist,dubbo的动态编译,dubbo的javassist)