Dubbo源码解析-SPI

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo源码解析-SPI相关的知识,希望对你有一定的参考价值。


dubbo源码解析-SPI机制

架构体系

框架介绍

概述

Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。

Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

相关概念

dubbo运行架构如下图示

Dubbo源码解析-SPI_加载

节点角色说明

节点

角色说明

​Provider​

暴露服务的服务提供方

​Consumer​

调用远程服务的服务消费方

​Registry​

服务注册与发现的注册中心

​Monitor​

统计服务的调用次数和调用时间的监控中心

​Container​

服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

关于dubbo 的特点分别有连通性、健壮性、伸缩性、以及向未来架构的升级性。特点的详细介绍也可以参考​​官方文档​​。

环境搭建

接下来逐步对dubbo各个模块的源码以及原理进行解析,目前dubbo框架已经交由Apache基金会进行孵化,被在github开源。

Dubbo 社区目前主力维护的有 2.6.x 和 2.7.x 两大版本,其中,

  • 2.6.x 主要以 bugfix 和少量 enhancements 为主,因此能完全保证稳定性
  • 2.7.x 作为社区的主要开发版本,得到持续更新并增加了大量新 feature 和优化,同时也带来了一些稳定性挑战

源码拉取

通过以下的这个命令签出最新的dubbo项目源码,并导入到IDEA中

git clone https://github.com/apache/dubbo.git dubbo

Dubbo源码解析-SPI_加载_02

可以看到Dubbo被拆分成很多的Maven项目,在后续课程中会介绍左边每个模块的大致作用。

环境导入

在本次课程中,不仅讲解dubbo源码还会涉及到相关的基础知识,为了方便学员快速理解并掌握各个内容,已经准备好了相关工程,只需导入到IDEA中即可。对于工程中代码的具体作用,在后续课程会依次讲解

测试

(1) 安装zookeeper

(2) 修改官网案例,配置zookeeper地址

(3) 启动服务提供者,启动服务消费者

架构体系

源码结构

通过如下图形可以大致的了解到,dubbo源码各个模块的相关作用:

Dubbo源码解析-SPI_java-zookeeper_03

模块说明:

  • dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
  • dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
  • dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
  • dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
  • dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
  • dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
  • dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。
  • dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。

整体设计

Dubbo源码解析-SPI_加载_04

图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明

  • config 配置层:对外配置接口,以 ​​ServiceConfig​​​, ​​ReferenceConfig​​ 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ​​ServiceProxy​​​ 为中心,扩展接口为 ​​ProxyFactory​
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 ​​RegistryFactory​​​, ​​Registry​​​, ​​RegistryService​
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 ​​Invoker​​​ 为中心,扩展接口为 ​​Cluster​​​, ​​Directory​​​, ​​Router​​​, ​​LoadBalance​
  • monitor 监控层:RPC 调用次数和调用时间监控,以 ​​Statistics​​​ 为中心,扩展接口为 ​​MonitorFactory​​​, ​​Monitor​​​, ​​MonitorService​
  • protocol 远程调用层:封装 RPC 调用,以 ​​Invocation​​​, ​​Result​​​ 为中心,扩展接口为 ​​Protocol​​​, ​​Invoker​​​, ​​Exporter​
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 ​​Request​​​, ​​Response​​​ 为中心,扩展接口为 ​​Exchanger​​​, ​​ExchangeChannel​​​, ​​ExchangeClient​​​, ​​ExchangeServer​
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 ​​Message​​​ 为中心,扩展接口为 ​​Channel​​​, ​​Transporter​​​, ​​Client​​​, ​​Server​​​, ​​Codec​
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 ​​Serialization​​​, ​​ObjectInput​​​, ​​ObjectOutput​​​, ​​ThreadPool​

SPI机制

在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

SPI的概述

SPI的主要作用

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

Dubbo源码解析-SPI_Java_05

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

入门案例

首先,我们定义一个接口,名称为 Robot。

public interface Robot 
void sayHello();

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot 

@Override
public void sayHello()
System.out.println("Hello, I am Optimus Prime.");



public class Bumblebee implements Robot

@Override
public void sayHello()
System.out.println("Hello, I am Bumblebee.");

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 com.itheima.java.spi.Robot。文件内容为实现类的全限定的类名,如下:

com.itheima.java.spi.impl.Bumblebee
com.itheima.java.spi.impl.OptimusPrime

做好所需的准备工作,接下来编写代码进行测试。

public class JavaSPITest 

@Test
public void sayHello() throws Exception
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);

最后来看一下测试结果,如下:

Dubbo源码解析-SPI_加载_06

从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。

总结

调用过程

  • 应用程序调用ServiceLoader.load方法,创建一个新的ServiceLoader,并实例化该类中的成员变量
  • 应用程序通过迭代器接口获取对象实例,ServiceLoader先判断成员变量providers对象中(LinkedHashMap<String,S>类型)是否有缓存实例对象,如果有缓存,直接返回。
    如果没有缓存,执行类的装载,

优点

使用 Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用进程可以根据实际业务情况启用或替换具体组件。

缺点

  • 不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  • 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用 ServiceLoader 类的实例是不安全的。
  • 加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

Dubbo中的SPI

概述

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。

入门案例

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。下面来演示 Dubbo SPI 的用法:

Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,配置内容如下。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

在使用Dubbo SPI 时,需要在接口上标注 @SPI 注解。

@SPI
public interface Robot
void sayHello();

通过 ExtensionLoader,我们可以加载指定的实现类,下面来演示 Dubbo SPI :

public class DubboSPITest 

@Test
public void sayHello() throws Exception
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();

测试结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfOvbsAX-1663579919850)(assets/1591798493318.png)]

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。

源码分析

上一章简单演示了 Dubbo SPI 的使用方法,首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

public T getExtension(String name) 
if (StringUtils.isEmpty(name))
throw new IllegalArgumentException("Extension name == null");

if ("true".equals(name))
// 获取默认的拓展实现类
return getDefaultExtension();

// Holder,顾名思义,用于持有目标对象
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// 双重检查
if (instance == null)
synchronized (holder)
instance = holder.get();
if (instance == null)
// 创建拓展实例
instance = createExtension(name);
// 设置实例到 holder 中
holder.set(instance);



return (T) instance;

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

private T createExtension(String name) 
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null)
throw findException(name);

try
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null)
// 通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);

// 向实例中注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses))
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses)
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));


return instance;
catch (Throwable t)
throw new IllegalStateException("...");

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。由于此类设计源码较多,这里简单的总结下ExtensionLoader整个执行逻辑:

getExtension(String name)  #根据key获取拓展对象
-->createExtension(String name) #创建拓展实例
-->getExtensionClasses #根据路径获取所有的拓展类
-->loadExtensionClasses #加载拓展类
-->cacheDefaultExtensionName #解析@SPI注解
-->loadDirectory #方法加载指定文件夹配置文件
-->loadResource #加载资源
-->loadClass #加载类,并通过 loadClass 方法对类进行缓存

SPI中的IOC和AOP

依赖注入

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:

private T injectExtension(T instance) 
try
if (objectFactory != null)
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods())
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers()))
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
try
// 获取属性名,比如 setName 方法对应属性名 name
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() +
method.getName().substring(4) : "";
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null)
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);

catch (Exception e)
logger.error("fail to inject via method...");




catch (Exception e)
logger.error(e.getMessage(), e);

return instance;

在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。这两个类的类的代码不是很复杂,这里就不一一分析了。

Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。

动态增强

在用Spring的时候,我们经常会用到AOP功能。在目标类的方法前后插入其他逻辑。比如通常使用Spring AOP来实现日志,监控和鉴权等功能。 Dubbo的扩展机制,是否也支持类似的功能呢?答案是yes。在Dubbo中,有一种特殊的类,被称为Wrapper类。通过装饰者模式,使用包装类包装原始的扩展点实例。在原始扩展点实现前后插入其他逻辑,实现AOP功能。

装饰者模式

装饰者模式:在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象上,从而实现动态拓展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

Dubbo源码解析-SPI_加载_07

一般来说装饰者模式有下面几个参与者:

  • Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为
  • ConcreteComponent:定义具体对象,即被装饰者
  • Decorator:抽象装饰者,继承自Component,从外类来扩展ConcreteComponent。对于ConcreteComponent来说,不需要知道Decorator的存在,Decorator是一个接口或抽象类
  • ConcreteDecorator:具体装饰者,用于扩展ConcreteComponent

注:装饰者和被装饰者对象有相同的超类型,因为装饰者和被装饰者必须是一样的类型,这里利用继承是为了达到类型匹配,而不是利用继承获得行为。

dubbo中的AOP

Dubbo AOP 是通过装饰者模式完成的,接下来通过一个简单的案例来学习dubbo中AOP的实现方式。

首先定义一个接口

package com.itheima.dubbo;

import org.apache.dubbo.common.extension.SPI;

@SPI
public interface Phone
void call();

定义接口的实现类,也就是被装饰者

package com.itheima.dubbo;

public class IphoneX implements Phone

@Override
public void call()
System.out.println("iphone正在拨打电话");

为了简单,这里省略了装饰者接口。仅仅定义一个装饰者,实现phone接口,内部配置增强逻辑方法

package com.itheima.dubbo;

public class MusicPhone implements Phone

private Phone phone;

public MusicPhone(Phone phone)
this.phone = phone;


@Override
public void call()
System.out.println("播放彩铃");
this.phone.call();

添加拓展点配置文件META-INF/dubbo/com.itheima.dubbo.Phone,内容如下

iphone = com.itheima.dubbo.IphoneX
filter = com.itheima.dubbo.MusicPhone

配置测试方法

public static void main(String[] args) 
ExtensionLoader<Phone> extensionLoader =
ExtensionLoader.getExtensionLoader(Phone.class);
Phone phone = extensionLoader.getExtension("iphone");
phone.call();

具体执行效果如下

Dubbo源码解析-SPI_dubbo_08

先调用装饰者增强,再调用目标方法完成业务逻辑。

通过测试案例,可以看到在Dubbo SPI中具有增强AOP的功能,我们只需要关注dubbo源码中这样一行代码就够了

//检查是否具有装饰者类,如果有调用装饰者类的构造方法,并返回实例对象
if (CollectionUtils.isNotEmpty(wrapperClasses))
for (Class<?> wrapperClass : wrapperClasses)
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));

动态编译

SPI中的自适应

我们知道在 Dubbo 中,很多拓展都是通过 SPI 机制 进行加载的,比如 Protocol、Cluster、LoadBalance、ProxyFactory 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载,即根据参数动态加载实现类。如下所示:

Dubbo源码解析-SPI_java-zookeeper_09

这种在运行时,根据方法参数才动态决定使用具体的拓展,在dubbo中就叫做扩展点自适应实例。其实是一个扩展点的代理,将扩展的选择从Dubbo启动时,延迟到RPC调用时。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。

自适应拓展机制的实现逻辑是这样的

  1. 首先 Dubbo 会为拓展接口生成具有代理功能的代码;
  2. 通过 javassist 或 jdk 编译这段代码,得到 Class 类;
  3. 通过反射创建代理类;
  4. 在代理类中,通过URL对象的参数来确定到底调用哪个实现类;

javassist入门

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。为了方便更好的理解dubbo中的自适应,这里通过案例的形式来熟悉下Javassist的基本使用

package com.itheima.compiler;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
* Javassist是一个开源的分析、编辑和创建Java字节码的类库
* 能动态改变类的结构,或者动态生成类
*/
public class CompilerByJavassist

public static void main(String[] args) throws Exception

// ClassPool:class对象容器
ClassPool pool = ClassPool.getDefault();

// 通过ClassPool生成一个User类
CtClass ctClass = pool.makeClass("com.itheima.domain.User");

// 添加属性 -- private String username
CtField enameField = new CtField(pool.getCtClass("java.lang.String"),
"username", ctClass);

enameField.setModifiers(Modifier.PRIVATE);

ctClass.addField(enameField);

// 添加属性 -- private int age
CtField enoField = new CtField(pool.getCtClass("int"), "age", ctClass);

enoField.setModifiers(Modifier.PRIVATE);

ctClass.addField(enoField);

//添加方法
ctClass.addMethod(CtNewMethod.getter("getUsername", enameField));
ctClass.addMethod(CtNewMethod.setter("setUsername", enameField));
ctClass.addMethod(CtNewMethod.getter("getAge", enoField));
ctClass.addMethod(CtNewMethod.setter("setAge", enoField));


// 无参构造器
CtConstructor constructor = new CtConstructor(null, ctClass);
constructor.setBody("");
ctClass.addConstructor(constructor);

// 添加构造函数
//ctClass.addConstructor(new CtConstructor(new CtClass[] , ctClass));

CtConstructor ctConstructor = new CtConstructor(new CtClass[] pool.get(String.class.getName()),CtClass.intType, ctClass);
ctConstructor.setBody("\\n this.username=$1; \\n this.age=$2;\\n");
ctClass.addConstructor(ctConstructor);

// 添加自定义方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printUser",new CtClass[] , ctClass);
// 为自定义方法设置修饰符
ctMethod.setModifiers(Modifier.PUBLIC);
// 为自定义方法设置函数体
StringBuffer buffer2 = new StringBuffer();
buffer2.append("\\nSystem.out.println(\\"用户信息如下\\");\\n")
.append("System.out.println(\\"用户名=\\"+username);\\n")
.append("System.out.println(\\"年龄=\\"+age);\\n").append("");
ctMethod.setBody(buffer2.toString());
ctClass.addMethod(ctMethod);

//生成一个class
Class<?> clazz = ctClass.toClass();

Constructor cons2 = clazz.getDeclaredConstructor(String.class,Integer.TYPE);

Object obj = cons2.newInstance("itheima",20);

//反射 执行方法
obj.getClass().getMethod("printUser", new Class[] )
.invoke(obj, new Object[] );

// 把生成的class文件写入文件
byte[] byteArr = ctClass.toBytecode();
FileOutputStream fos = new FileOutputStream(new File("D://User.class"));
fos.write(byteArr);
fos.close();

通过以上代码,我们可以知道使用javassist可以方便的在运行时,按需动态的创建java对象,并执行内部方法。而这也是dubbo中动态编译的核心

源码分析

Adaptive注解

在开始之前,我们有必要先看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE, ElementType.METHOD)
public @interface Adaptive
String[] value() default ;

从上面的代码中可知,Adaptive 可注解在类或方法上。

  • 标注在类上:Dubbo 不会为该类生成代理类。
  • 标注在方法上:Dubbo 则会为该方法生成代理逻辑,表示当前方法需要根据 参数URL 调用对应的扩展点实现。

获取自适应拓展类

dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。 先来看下创建自适应扩展类的代码:

public T getAdaptiveExtension() 
Object instance = cachedAdaptiveInstance.get();
if (instance == null)
synchronized (cachedAdaptiveInstance)
instance = cachedAdaptiveInstance.get();
if (instance == null)
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);




return (T) instance;

继续看createAdaptiveExtension方法

private T createAdaptiveExtension()         
return injectExtension((T) getAdaptiveExtensionClass().newInstance());

继续看getAdaptiveExtensionClass方法

private Class<?> getAdaptiveExtensionClass() 
getExtensionClasses();
if (cachedAdaptiveClass != null)
return cachedAdaptiveClass;

return cachedAdaptiveClass = createAdaptiveExtensionClass();

继续看createAdaptiveExtensionClass方法,绕了一大圈,终于来到了具体的实现了。看这个createAdaptiveExtensionClass方法,它首先会生成自适应类的Java源码,然后再将源码编译成Java的字节码,加载到JVM中。

private Class<?> createAdaptiveExtensionClass() 
String code = createAdaptiveExtensionClassCode();
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);

Compiler的代码,默认实现是javassist。

@SPI("javassist")
public interface Compiler
Class<?> compile(String code, ClassLoader classLoader);

createAdaptiveExtensionClassCode()方法中使用一个StringBuilder来构建自适应类的Java源码。方法实现比较长,这里就不贴代码了。这种生成字节码的方式也挺有意思的,先生成Java源代码,然后编译,加载到jvm中。通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。因为xxx.java文件是Java通用的,也是我们最熟悉的。只是代码的可读性不强,需要一点一点构建xx.java的内容。

ssCode();
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);
Compiler的代码,默认实现是javassist。

```java
@SPI("javassist")
public interface Compiler
Class<?> compile(String code, ClassLoader classLoader);

createAdaptiveExtensionClassCode()方法中使用一个StringBuilder来构建自适应类的Java源码。方法实现比较长,这里就不贴代码了。这种生成字节码的方式也挺有意思的,先生成Java源代码,然后编译,加载到jvm中。通过这种方式,可以更好的控制生成的Java类。而且这样也不用care各个字节码生成框架的api等。因为xxx.java文件是Java通用的,也是我们最熟悉的。只是代码的可读性不强,需要一点一点构建xx.java的内容。


以上是关于Dubbo源码解析-SPI的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo源码解析-SPI

Dubbo源码解析-SPI

dubbo源码解析

dubbo源码解析-spi

Dubbo 源码解析02_Dubbo SPI

2.2 dubbo-spi源码解析