Dubbo——扩展点动态编译的实现

扩展点动态编译的实现

Dubbo SPI的自适应特性让整个框架非常灵活,而动态编译又是自适应特性的基础,因为动态生成的自适应类只是字符串,需要通过编译才能得到真正的Class。虽然我们可以使用反射来动态代理一个类,但是在性能上和直接编译好的Class会有一定差距。Dubbo SPI通过代码的动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。

总体结构

Dubbo中有三种代码编译器,分别是JDK编译器、Javassist编译器和AdaptiveCompiler编译器。这几种编译器都实现了Compiler接口,编译器类之间的关系如下图:

在这里插入图片描述
Compiler接口上含有一个SPI注解,注解的默认值是@SPI("javassist"),很明显,Javassist编译器将作为默认编译器。如果用户想改变默认编译器,则可以通过<dubbo:application compiler="jdk" />标签进行设置。

AdaptiveCompiler上面有@Adaptive注解,说明AdaptiveCompiler会固定为默认实现,这个Compiler的主要作用和AdaptiveExtensionFactory相似,就是为了管理其他compiler,如下图所示:
在这里插入图片描述

AdaptiveCompiler#setDefaultCompiler方法会在ApplicationConfig中被调用,也就是Dubbo在启动时,救护解析<dubbo:application compiper="jdk" />标签,获取设置的值,初始化对应的编译器。如果没有标签设置,则使用@SPI("javassist")中的设置,即javassistCompiler。

然后看一下AbstractCompiler,它是一个抽象类,无法实例化,但在里面封装了通用的模板逻辑。还定义了一个抽象方法doCompile,留给子类实现的编译逻辑。JavassistCompiler和JDKCompiler都实现了这个抽象方法。

AbstractCompiler的主要抽象逻辑如下:

  1. 通过正则匹配出包路径、类名,再根据包路径、类名拼接处全路径类名。
  2. 尝试通过Class.forName记载该类并返回,防止重复编译。如果类加载器中没有该类,则进入第三步。
  3. 调用doCompiler方法进行编译。

Javassist动态代码编译

Java中动态生成Class的方式有恩多,可以直接基于字节码的方式生成,常见的工具库有CGLIB、ASM、Javassist等。而自适应扩展点使用了生成字符串代码再编译为Class的方式。

Javassist使用示例:

@Test
public void test_javassist() throws CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    //初始化Javassist类池
    ClassPool classPool = ClassPool.getDefault();
    //创建一个Hello World类
    CtClass ctClass = classPool.makeClass("Hello World");
    //添加一个test方法,会打印Hello World,直接传入方法的字符串
    CtMethod method = CtMethod.make("" +
            "public static void test(){" +
            "System.out.println(\"Hello World\");" +
            "}", ctClass);
    ctClass.addMethod(method);
    //生成类
    Class aClass = ctClass.toClass();
    //通过反射调用这个类实例
    Object object = aClass.newInstance();
    Method m = aClass.getDeclaredMethod("test", null);
    m.invoke(object, null);
}

在这里插入图片描述

由于之前已经生成了代码字符串,因此在JavassistCompiler中,就是不断通过正则表达式匹配不同部位的代码,然后调用Javassist库中的API生成不同部位的代码,最后得到一个完整的Class对象。

具体步骤如下:

  1. 初始化Javassist,设置默认参数,如设置当前的classpath。
  2. 通过正则匹配出所有import的包,并使用Javassist添加import。
  3. 通过正则匹配出所有extends的包,创建Class对象,并使用Javassist添加extends。
  4. 通过正则匹配出所有implements包,并使用Javassist添加implements。
  5. 通过正则匹配出类里面所有内容,即得到{}中的内容,再通过正则匹配出所有方法,并使用Javassist添加类方法。
  6. 生成Class对象。

JDK动态代码编译

JdkCompiler是Dubbo编译器的另一种实现,使用了JDK自带的编译器,原生JDK编译器包位于java.tools下。主要使用了三个东西:JavaFileObject接口、ForwardingJavaFileManager接口、JavaCompiler.CompilationTask方法

整个动态编译过程可以简单地总结为:首先初始化一个JavaFileObject对象,并把字符串作为参数传入构造方法,然后调用JavaCompiler.CompilationTask方法编译出具体的类。JavaFileManager负责管理类文件输入/输出的位置。

  1. JavaFileObject接口:字符串代码会被包装成一个文件对象,并提供获取二进制流的接口。Dubbo框架中的JavaFileObjectImpl类可以看做该接口的一种扩展实现,构造方法中需要传入生成好的字符串代码,此文件对象的输入和输入都是ByteArray流。

  2. JavaFileManager接口: 主要管理文件的读取和输出位置。JDK中没有可以直接使用的实现类,唯一的实现鳄梨ForwardingJavaFileManager构造器又是protect类型。因此Dubbo中定制化实现了一个JavaFileManagerImpl类,并通过一个自定义类加载器ClassLoaderImpl完成资源加载。

  3. JavaCompiler.CompilationTask:把JavaFileObject对象编译层具体的类。

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值