深度解析Dubbo的可扩展机制SPI源码:从理论到实践,打造高效、稳定的分布式服务框架

本文详细介绍了Dubbo中的SPI架构,特别是ExtensionLoader的工作原理,如何获取和创建扩展点实例,以及Adaptive和Activate扩展点的区别。此外,还涵盖了Dubbo的依赖注入和AOP实现方式。
摘要由CSDN通过智能技术生成

Dubbo SPI架构图

Demo

// 获取Protocol的ExtensionLoader实例
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

// 通过ExtensionLoader获取名为"dubbo"的Protocol扩展点实例
Protocol dubboProtocol = extensionLoader.getExtension("dubbo");

// 打印获取到的Protocol扩展点实例
System.out.println(dubboProtocol);

        上面这个Demo 就是Dubbo 常见的写法,表示获取"dubbo"  对应的Protocol  扩展点。Protocol   是一个接口 

        在ExtensionLoader  类的内部有一个staticConcurrentHashMap,    用来缓存某个接口类型所对应的ExtensionLoader 实例

ExtensionLoader

ExtensionLoader表示某个接口的扩展点加载器, 可以用来加载某个扩展点实例。

ExtensionLoader中除开有上文的staticMap外, 还有两个非常重要的属性:

1. Class<?> type 表示当前ExtensionLoader实例是哪个接口的扩展点加载器

2. ExtensionFactory objectFactory 扩展点工厂  对象工厂  可以获得某个对象

ExtensionLoaderExtensionFactory的区别在于:

1. ExtensionLoader最终所得到的对象是Dubbo SPI机制产生的

2. ExtensionFactory最终所得到的对象可能是Dubbo SPI机制所产生的, 也可能是从Spring容器中所获 得的对象ExtensionLoader中有三个常用的方法:

1. getExtension( "dubbo "): 表示获取名字为dubbo的扩展点实例

2. getAdaptiveExtension(): 表示获取一 个自适应的扩展点实例

3. getActivateExtension(URL url, String[] values, String group): 表示一 个可以被url激活的扩展点 实例, 后文详细解释

其中, 什么是自适应扩展点实例? 它其实就是当前这个接口的一 代理对象。

// 创建一个ExtensionLoader实例,用于加载Protocol接口的扩展实现
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

// 通过ExtensionLoader实例获取名为"dubbo"的Protocol扩展实现
Protocol protocol = extensionLoader.getExtension("dubbo");

        当我们调用上述代码, 我们会将得到一 DubboProtocol的实例对象, 但在getExtension()方法中,Dubbo会对DubboProtocol对象进行依赖注入  也就是自动给属性赋值  属性的类型为一 个接口 , 记为A  接口  这个时候, 对于Dubbo来说它并不知道该给这个属性赋什么值, 换句话说, Dubbo并不知道在进 行依赖注入时该找一 个什么的的扩展点对象给这个属性, 这时就会预先赋值一 个A接口的自适应扩展点实   例, 也就是A接口的一 个代理对象。

        后续, 在A接口的代理对象被真正用到时, 才会结合URL信息找到真正的A接口对应的扩展点实例进行调  

       

getExtension(String name)方法

在调用getExtension   去获取一个扩展点实例后,会对实例进行缓存,下次再获取同样名字的扩展点实例时 就会从缓存中拿了。

createExtension(String name)方法

在调用createExtension(String       name)方法去创建一个扩展点实例时,要经过以下几个步骤:

1.根据name 找到对应的扩展点实现类

2. 根据实现类生成一个实例, 把实现类和对应生成的实例进行缓

3. 对生成出来的实例进行依赖注入(给实例的属性进行赋值)

4. 对依赖注入后的实例进行AOP(Wrapper),    把当前接口类的所有的Wrapper 全部一层一层包裹在实例对象上,没包裹个Wrapper 后,也会对Wrapper 对象进行依赖注入

5. 返回最终的Wrapper 对象

getExtensionClasses

getExtensionClasses()是用来加载当前接口所有的扩展点实现类的, 返回一 Map 。之后可以从这个 Map中按照指定的name获取对应的扩展点实现类。

当把当前接口的所有扩展点实现类都加载出来后也会进行缓存, 下次需要加载时直接拿缓存中的。

Dubbo在加载一 个接口的扩展点时, 思路是这样的:

1. 根据接口的全限定名去META-INF/dubbo/internal/目录下寻找对应的文件, 调用loadResource方法 进行加载

2. 根据接口的全限定名去META-INF/dubbo/目录下寻找对应的文件, 调用loadResource方法进行加载

3. 根据接口的全限定名去META-INF/services/目录下寻找对应的文件, 调用loadResource方法进行加 载

这里其实会设计到老版本兼容的逻辑, 不解释了。

loadResource方法

loadResource方法就是完成对文件内容的解析, 按行进行解析, 会解析出 "= "两边的内容, "= "左边的内容 就是扩展点的name 右边的内容就是扩展点实现类, 并且会利用ExtensionLoader类的类加载器来加载扩 展点实现类。

然后调用loadClass方法对name和扩展点实例进行详细的解析, 并且最终把他们放到Map中去。

loadClass方法

loadClass方法会做如下几件事情:

1. 当前扩展点实现类上是否存在@Adaptive注解 如果存在则把该类认为是当前接口的默认自适应类   口代理类), 并把该类存到cachedAdaptiveClass属性上。

2. 当前扩展点实现是否是一 个当前接口的一 Wrapper类, 如果判断的? 就是看当前类中是否存在一 个构 造方法, 该构造方法只有一 个参数, 参数类型为接口类型, 如果存在这一 的构造方法, 那么这个类就是 该接口的Wrapper类, 如果是, 则把该类添加到cachedWrapperClasses中去,cachedWrapperClasses是一 set

3. 如果不是自适应类, 或者也不是Wrapper类, 则判断是有存在name 如果没有name 报错。

4. 如果有多个name 则判断一 下当前扩展点实现类上是否存在@Activate注解, 如果存在, 则把该类添 加到cachedActivates中, cachedWrapperClasses是一 map

5. 最后, 遍历多个name 把每个name和对应的实现类存到extensionClasses中去, extensionClasses 就是上文所提到的map

至此, 加载类就走完了。

回到createExtension(String name)方法中的逻辑, 当前这个接口的所有扩展点实现类都扫描完了之后  就可以根据用户所指定的名字, 找到对应的实现类了, 后进行实例化, 然后进行IOC(依赖注入)和AOP

Dubbo中的IOC

1. 根据当前实例的类, 找到这个类中的setter方法, 进行依赖注入

2. 先分析出setter方法的参数类型pt

3. 在截取出setter方法所对应的属性名property

4. 调用objectFactory.getExtension(pt, property)得到一 个对象, 这里就会从Spring容器或通过

DubboSpi机制得到一 个对象, 比较特殊的是, 如果是通过DubboSpi机制得到的对象, pt这个类型 的一 个自适应对象(代理对象)。

5. 再反射调用setter方法进行注入

Dubbo中的AOP

dubbo中也实现了一 套非常简单的AOP 就是利用Wrapper 如果一 个接口的扩展点中包含了多个Wrapper类, 那么在实例化完某个扩展点后, 就会利用这些Wrapper类对这个实例进行包裹, 比如: 现在 有一 DubboProtocol的实例, 同时对于Protocol这个接口还有很多的Wrapper 比如ProtocolFilterWrapper ProtocolListenerWrapper 那么, 当对DubboProtocol的实例完成了IOC    后, 就会先调用new ProtocolFilterWrapper(DubboProtocol实例)生成一 个新的Protocol的实例, 再对   此实例进行IOC 完了之后, 会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成  个新的Protocol的实例, 然后进行IOC 从而完成DubboProtocol实例的AOP

自适应扩展点补充上面提到的自适应扩展点对象, 也就是某个接口的代理对象是通过Dubbo内部生成代理类, 然后生成代理 对象的。

额外的, Dubbo中还设计另外一 种机制来生成自适应扩展点, 这种机制就是可以通过@Adaptive注解来指定某个类为某个接口的代理类, 如果指定了, Dubbo在生成自适应扩展点对象时实际上生成的就是 @Adaptive注解所注解的类的实例对象。

如果是由Dubbo默认实现的, 那么我们就看看Dubbo是如何生成代理类的。

createAdaptiveExtensionClass方法

createAdaptiveExtensionClass方法就是Dubbo中默认生成Adaptive类实例的逻辑 。说白了 这个实例 就是当前这个接口的一 个代理对象 。比如下面的代码:

// 创建一个ExtensionLoader实例,用于加载Protocol接口的扩展实现
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

// 通过ExtensionLoader实例获取Protocol接口的自适应扩展实现
Protocol protocol = extensionLoader.getAdaptiveExtension();

这个代码就是Protocol接口的一 个代理对象, 那么代理逻辑就是在new

AdaptiveClassCodeGenerator(type, cachedDefaultName) .generate()方法中。

1. type就是接口

2. cacheDefaultName就是该接口默认的扩展点实现的名字

看个例子, Protocol接口的Adaptive类:

package org.apache.dubbo.rpc;

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

// Protocol$Adaptive类实现了Protocol接口,它是Protocol的自适应扩展实现
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

    // destroy方法在这里并未实现,如果被调用会抛出UnsupportedOperationException
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    // getDefaultPort方法在这里并未实现,如果被调用会抛出UnsupportedOperationException
    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    // export方法用于导出服务,它接收一个Invoker参数
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");

        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys ([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);

        return extension.export(arg0);
    }

    // refer方法用于引用服务,它接收一个Class参数和一个URL参数
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

        if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys ([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);

        return extension.refer(arg0, arg1);
    }
}

可以看到, Protocol接口中有四个方法, 但是只有exportrefer两个方法进行代理 。为什么? 因为 Protocol接口中在export方法和refer方法上加了@Adaptive注解 。但是, 不是只要在方法上加了

@Adaptive注解就可以进行代理, 还有其他条件, 比如:

1. 该方法如果是无参的, 那么则会报错

2. 该方法有参数, 可以有多个, 并且其中某个参数类型是URL 那么则可以进行代理 3. 该方法有参数, 可以有多个, 但是没有URL类型的参数, 那么则不能进行代理

4. 该方法有参数, 可以有多个, 没有URL类型的参数, 但是如果这些参数类型, 对应的类中存在getUrl   返回值类型为URL), 那么也可以进行代理

所以, 可以发现, 某个接口的Adaptive对象, 在调用某个方法时, 是通过该方法中的URL参数,通过调用  ExtensionLoader.getExtensionLoader(com. luban.Car.class) .getExtension(extName);得到一 个扩展 点实例, 然后调用该实例对应的方法。

Activate扩展点

上文说到, 每个扩展点都有一 name 通过这个name可以获得该name对应的扩展点实例, 但是有的场景 下, 希望一 次性获得多个扩展点实例

// 创建一个ExtensionLoader实例,用于加载Filter接口的扩展实现
ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);

// 创建一个URL实例,并添加一个名为"cache"的参数,值为"test"
URL url = new URL("http", "localhost", 8080);
url = url.addParameter("cache", "test");

// 通过ExtensionLoader实例获取满足特定条件的Filter扩展实现
// 这里的条件是:URL中包含名为"validation"的参数,且角色为CONSUMER
List<Filter> activateExtensions = extensionLoader.getActivateExtension(url, new String[]{"validation"}, CommonConstants.CONSUMER);

// 遍历并打印出所有满足条件的Filter扩展实现
for (Filter activateExtension : activateExtensions) {
    System.out.println(activateExtension);
}

运行这段代码后,会找到5个Filter扩展实现:

  1. org.apache.dubbo.rpc.filter.ConsumerContextFilter
  2. org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter
  3. org.apache.dubbo.monitor.support.MonitorFilter
  4. org.apache.dubbo.cache.filter.CacheFilter
  5. org.apache.dubbo.validation.filter.ValidationFilter

前三个是通过CommonConstants.CONSUMER找到的

CacheFilter是通过url中的参数找到的

ValidationFilter是通过指定的name找到的

在一 个扩展点类上, 可以添加@Activate注解, 这个注解的属性有:

1. String[] group(): 表示这个扩展点是属于哪组的, 这里组通常分为PROVIDERCONSUMER 表示 该扩展点能在服务提供者端, 或者消费端使用

2. String[] value(): 表示的是URL中的某个参数key 当利用getActivateExtension方法来寻找扩展点  时, 如果传入的url中包含的参数的所有key中, 包括了当前扩展点中的value值, 那么则表示当前url 以使用该扩展点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

光芒软件工匠

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

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

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

打赏作者

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

抵扣说明:

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

余额充值