Dubbo 源码分析 – 自适应拓展原理

1.原理

我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心。Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并非想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,就无法进行加载,这似乎是个死结。不过好在也有相应的解决办法,通过代理模式就可以解决这个问题,这里我们将具有代理功能的拓展称之为自适应拓展。Dubbo 并未直接通过代理模式实现自适应拓展,而是代理代理模式基础上,封装了一个更炫的实现方式。Dubbo 首先会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后在通过反射创建代理类。整个过程比较复杂、炫丽。如此复杂的过程最终的目的是为拓展生成代理对象,但实际上每个代理对象的代理逻辑基本一致,均是从 URL 中获取欲加载实现类的名称。因此,我们完全可以把代理逻辑抽出来,并通过动态代理的方式实现自适应拓展。这样做的好处显而易见,方便维护,也方便源码学习者学习和调试代码。本文将在随后实现一个动态代理版的自适应拓展,有兴趣的同学可以继续往下读。

接下来,我们通过一个示例演示自适应拓展类。这个示例取自 Dubbo 官方文档,我这里进行了一定的拓展。这是一个与汽车相关的例子,我们有一个车轮制造厂接口 WheelMaker:

public interface WheelMaker {
    Wheel makeWheel(URL url);
}

WheelMaker 接口的 Adaptive 实现类如下:

public class AdaptiveWheelMaker implements WheelMaker {
    public Wheel makeWheel(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }

        // 1.从 URL 中获取 WheelMaker 名称
        String wheelMakerName = url.getParameter("Wheel.maker");
        if (name == null) {
            throw new IllegalArgumentException("wheelMakerName == null");
        }

        // 2.通过 SPI 加载具体的 WheelMaker
        WheelMaker wheelMaker = ExtensionLoader
            .getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);

        // 3.调用目标方法
        return wheelMaker.makeWheel(URL url);
    }
}

AdaptiveWheelMaker 是一个代理类,它主要做了三件事情:

  1. 从 URL 中获取 WheelMaker 名称
  2. 通过 SPI 加载具体的 WheelMaker
  3. 调用目标方法

接下来,我们来看看汽车制造厂 CarMaker 接口与其实现类。

public interface CarMaker {
    Car makeCar(URL url);
}

public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;

    // 通过 setter 注入 AdaptiveWheelMaker
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }

    public Car makeCar(URL url) {
        Wheel wheel = wheelMaker.makeWheel(url);
        return new RaceCar(wheel, ...);
    }
}

RaceCarMaker 持有一个 WheelMaker 类型从成员变量,在程序启动时,我们可以将 AdaptiveWheelMaker 通过 setter 方法注入到 RaceCarMaker 中。在运行时,假设有这样一个 URL 类型的参数:

dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker

RaceCarMaker 的 makeCar 方法将上面的 url 作为参数传给 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法从 url 中提取 wheel.maker 参数,得到 MichelinWheelMaker。之后再通过 SPI 加载名为 MichelinWheelMaker 的实现类,得到具体的 WheelMaker 实例。

上面这个示例展示了自适应拓展类的核心实现 – 在组件方法被调用时,通过代理的方式加载指定的实现类,并调用被代理的方法。

经过以上说明,大家应该搞懂了自适应拓展的原理。接下来,我们深入到源码中,探索自适应拓展生成的过程。

2.源码分析

在对自适应拓展生成过程进行深入分析之前,我们先来看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。该注解的定义如下:

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

从上面的代码中可知,Adaptive 可注解在类或方法上。注解在类上时,Dubbo 不会为该类生成代理类。注解上方法(接口方法)上时,Dubbo 会为为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本章将会重点分析此块逻辑。接下来,我们从 getAdaptiveExtension 方法进行分析。代码如下:

2.1 获取自适应拓展

public T getAdaptiveExtension() {
    // 从缓存中获取自适应拓展
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {    // 缓存未命中
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // 创建自适应拓展
                        instance = createAdaptiveExtension();
                        // 设置拓展到缓存中
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("...");
                    }
                }
            }
        } else {
            throw new IllegalStateException("...");
        }
    }

    return (T) instance;
}

getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展。下面,我们看一下 createAdaptiveExtension 方法的代码。

private T createAdaptiveExtension() {
    try {
        // 获取自适应拓展类,并通过反射实例化
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("...");
    }
}

createAdaptiveExtension 方法代码比较少,但却包含了三个动作,分别如下:

  1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 调用 injectExtension 方法向拓展实例中注入依赖

前两个动作比较好理解,第三个动作不好理解,这里简单说明一下。injectExtension 方法通过 setter 方法向目标对象中注入依赖,可以看做是一个简单 IOC 的实现。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的 Adaptive 拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,我在上一篇文章中已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。

private Class<?> getAdaptiveExtensionClass() {
    // 通过 SPI 获取所有的拓展类
    getExtensionClasses();
    // 检查缓存,若缓存不为空,则直接返回缓存
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 创建自适应拓展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getAdaptiveExtensionClass 方法也包含了三个步骤,如下:

  1. 调用 getExtensionClasses 获取所有的拓展类
  2. 检查缓存,若缓存不为空,则返回缓存
  3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类

这三个步骤看起来平淡无奇,似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了一些细节,需要说明一下。首先从第一个步骤说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:

private Class<?> createAdaptiveExtensionClass() {
    // 构建自适应拓展代码
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    // 获取编译器实现类
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // 编译代码,生成 Class
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我将重点分析代理类代码生成逻辑。至于代码编译的过程,并非本文范畴,这里就不分析了,大家有兴趣可以自己看看。下面,我们把目光聚焦在 createAdaptiveExtensionClassCode 方法上。

2.2 自适应拓展类代码生成

createAdaptiveExtensionClassCode 方法代码略多,约有两百行代码。因此在本节中,我将会对该方法的代码进行拆分分析,以帮助大家更好的理解代码含义。

2.2.1 Adaptive 注解检测

在生成代理类源码之前,createAdaptiveExtensionClassCode 方法首先会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常。相关代码如下:


                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值