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

发布时间:2024年01月16日

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?以使用该扩展点。

文章来源:https://blog.csdn.net/oWuChenHua/article/details/135632622
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。