Dubbo源码分析系列-Dubbo的动态编译原理

导语
  Java程序时运行在JVM中的Class文件,在一般的情况下都是把左右的Java文件编写完成之后,统一进行编译操作,做完编译操作之后,统一由JVM进行加载。而所谓的动态编译则是在JVM进程运行的过程中把源文件编译为字节码文件,然后使用字节码文件创建对象实例。在Dubbo中提供了一个SPI接口,通过这个接口来动态的生成对应的适配器类。下面就来看看在Dubbo中是如何生成这个适配器类

  在DubboSPI机制中,对于编译提供了下面一个SPI的扩展。Dubbo中有对于该接口的扩展有两个org.apache.dubbo.common.compiler.support.JavassistCompiler 和 org.apache.dubbo.common.compiler.support.JdkCompiler。

@SPI("javassist")
public interface Compiler {

    /**
     * Compile java source code.
     *
     * @param code        Java source code
     * @param classLoader classloader
     * @return Compiled class
     */
    Class<?> compile(String code, ClassLoader classLoader);

}

&emps; 下面就从Dubbo框架如何使用动态编译生成扩展接口对应的适配器开始入手,首先在之前分析SPI机制的时候提到过一个关于扩展类加载的机制。org.apache.dubbo.common.extension.ExtensionLoader ,在这个类中有一个Class<?> createAdaptiveExtensionClass() 的私有方法。

private Class<?> createAdaptiveExtensionClass() {
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

代码生成

  会看到最终这个方法要返回的是一个Class的的对象,得到这个对象之后就可以使用newInstance()方法创建一个新的实例。那么在这个之前所要做的事情是什么呢?这个就来进一步的分析一下。这里以Dubbo协议扩展为例来进行分析。

  在上面方法中有一个AdaptiveClassCodeGenerator类,表示Adaptive类代码生成器。并且调用了下面这段代码
在这里插入图片描述
  首先代码进入之后先进行了一个判断,就是看是否是有Adaptive注解标注的方法,如果没有的话就抛出一次样提示没有Adaptive注解的方法无法进行扩展。如果有这个注解就表示可以进行扩展,就按照如下的规则进行一个扩展,最后生成一个字符串。从这个规则来看最后所生成的字符串就是一个Java文件的字符串。也就是在上面方法做完第一步操作之后最后的产出物就是一个Java代码。
在这里插入图片描述

类加载

  从上面代码中可以看到,在进行完操作之后调用了ClassLoader classLoader = findClassLoader(); 方法,也就是所进行了一次加载。这个操作就是为了获取到一个ClassLoader对象。
在这里插入图片描述
  而这个对象是通过下面这个方法进行获取的。会发现这其实就是我们普通的类加载器调用的方法。只不过,这里有个逻辑就是,使用传入参数类的类加载器进行加载。也就是ExtensionLoader的类加载器时谁,获取到的这个类加载就可以加载与该类相关的东西。
在这里插入图片描述

进行动态编译

  进过上面的操作之后获得了一个ClassLoader ,然后就进行了如下的操作
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

  可以看到,上面的操作使用了一个链式调用。首先来看看看第一个方法
在这里插入图片描述
  从这个方法中可以看到传入的参数是一个Class类型的对象,并且可以看到在最后对于主节的判断中判断了是否是@SPI标记的类,如果上面三个条件都满足则进入到一个加载的过程中

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

  在这个地方简单的来说一下,这个方法,表示的意思。一般的在做缓存的时候,我们都知道,如果一个东西已经存在于缓存中,那么这个时候我们就不需要在进行缓存操作了,但是如果在我们操作的过程中出现了一个新的东西,这个东西没有在缓存中,这时候我们需要做的操作就是先使用这个东西,然后将新的东西更新到缓存中。后续如果有同样的东西时候的时候就可以直接进行使用了。
在这里插入图片描述
  上面的操作所做的一件事情,就是如果出现了新的类加载器就将新的类加载器放入到缓存中,如果不是则使用已有的加载器进行操作。
  到这里第一方法就已经知道了,就是获取到一个扩展的加载器。接下来就看第二个方法。
在这里插入图片描述
  从图中可以看到,在所有操作之前其实是有一个cachedAdaptiveInstance 的缓存存在的。调用该方法之后,首先会进入到缓存中获取,如果获取不到则会看是否有创建Adaptive实例的异常,如果没有则进行下一步的获取操作。这里使用到了锁机制,会看到其实这里的Instance使用了双重检测机制。进行了两次判空两次获取操作。第一次是大概就获取了一下这个,第二次就是非常精细的操作,这样做的好处就是,避免了在进入锁的时候在锁内部出现异常,导致整个的死锁。会看到在创建的时候调用了如下的方法

在这里插入图片描述
  在上方法调用完成之后其实是下面这个方法起作用。
在这里插入图片描述

返回结果

  分析到这里大概根据代码结构就知道了,最后的返回结果调用了return compiler.compile(code, classLoader); 这个地方传入的两个参数分别就是生成的Java代码的字符串和对应的类加载器。根据一贯的框架编写原则我们知道compile()方法其实是不做具体的编译的工作的,真正做编译工作的其实是一个叫做doComplie()的方法也就是在JavassistCompiler和JdkCompiler类中的doCompile()方法。这里使用JavassistCompiler类来做说明。
在这里插入图片描述
  这个方法其实所做的工作其实就是类加载机制中所做的事情,加载、连接、初始化,连接又包括验证、准备、解析。整个的整个的工作都是围绕这个核心来展开。

总结

  上面内容简单的分析了一下Dubbo动态编译的相关内容,在Dubbo中会为每个扩展接口都生成器对应的适配器类的源码,然后通过选择具体的动态编译的扩展类来实现对源码进行的动态运行时编译,最终生成一个可用的对象实例。这个机制其实在Java的SPI机制中就已经用到了,在Dubbo增强版的SPI中对这个机制进行了进一步的优化操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nihui123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值