美文网首页
Dubbo的SPI机制

Dubbo的SPI机制

作者: 就这些吗 | 来源:发表于2020-07-12 02:17 被阅读0次

本系列参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。阅读本文前可以参考阅读下面链接里的内容。Dubbo版本为2.6.1。
JavaSPI机制简介
Dubbo官网文档—扩展点加载

文章内容顺序:
1.Dubbo 自己实现了一套 SPI 机制,而不是使用 Java 标准的 SPI 。为什么要自己实现?
2.@SPI简单介绍
3.Dubbo SPI机制是怎么实现加载用到的类,而不是像JDK SPI中加载全部?
4.Dubbo SPI 机制是怎么实现AOP的?
5.如果一个扩展接口有多个包装类,怎么办?比如Protocol这个扩展点,他有两个Wrapper,该怎么生成?
6.那么Dubbo SPI是怎么实现IOC的呢?
7.如果扩展接口有多个实现类,具体注入哪个呢?引出@Adaptive
8.@Adaptive介绍,包括入口方法到最后的链路调用,样例代码的生成。
9.为什么源码里只有AdaptiveExtensionFactory和AdaptiveCompiler注解了?
10.为什么AdaptiveCompiler这个类是固定已知的?包括这个类的源码介绍
11.为什么AdaptiveExtensionFactory这个类是固定已知的?包括这个类的源码介绍
12@Activate注解用途,入口方法介绍

1.Dubbo 自己实现了一套 SPI 机制,而不是使用 Java 标准的 SPI 。为什么要自己实现?

  • 1.Dubbo 有很多的拓展点,例如 Protocol、Filter 等等。并且每个拓展点有多种的实现,例如 Protocol 有 DubboProtocol、InjvmProtocol、RestProtocol 等等。那么使用 JDK SPI 机制,会初始化无用的拓展点及其实现,造成不必要的耗时与资源浪费。
  • 2.Dubbo解决了JDK SPI机制吞异常的机制。
    例如:如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 3.增加了对扩展点 IoC(留待下面说明) 和 AOP (通过wapper实现,装饰器设计模式)的支持,一个扩展点可以直接 setter 注入其它扩展点。

这种扩展机制有三个注解,分别是@SPI、@Adaptive 、@Activate。我们将会将SPI、Adaptive放一块讲,Activate单独拎出来讲。

这些机制主要在com.alibaba.dubbo.common.extension.ExtensionLoader类中。
类主要方法和功能如下


image.png

2.@SPI

@SPI 主要用于标记该接口是SPI接口,即扩展点,可以有多个不同内置或用户定义的实现,下面是Dubbo的一个Protocol接口

@SPI("dubbo")
public interface Protocol {
    // ... 省略代码
}

他的扩展点如下:


image.png

上面的是Protocol的接口,@SPI整个注解的意义是用来说明这个是扩展类,并且默认的实现是DubboProtocol,可以通过修改XML文件中的来进行配置。

<dubbo:protocol name="dubbo" port="20800" />

3.Dubbo SPI机制是怎么实现加载用到的类,而不是像JDK SPI中加载全部?

image.png

这是 SPI 的配置目录,通过这个文件来加载SPI的加载对象。

而一个拓展( 拓展接口 )对应一个 ExtensionLoader 对象。例如,Protocol 和 Filter 分别对应一个 ExtensionLoader 中的对象。如下图红框所示


image.png

一个拓展接口通过其 ExtensionLoader 对象,加载它的拓展实现们。我们会发现多个属性都是 “cached“ 开头(图里只展示了一点)。ExtensionLoader 考虑到性能和资源的优化,读取拓展配置后,会首先进行缓存。等到 Dubbo 代码真正用到对应的拓展实现时,进行拓展实现的对象的初始化。并且,初始化完成后,也会进行缓存。也就是说:

  • (1).缓存加载的拓展配置
  • (2).缓存创建的拓展实现对象
    通过这种方式,就不用一开始加载就初始化所有对象,而是等需要他们的时候再初始化。

4.Dubbo SPI 机制是怎么实现AOP的?

com.alibaba.dubbo.common.extension包下的ExtensionLoader类中的loadFile()实现(方法过长,就不贴了),此方法用来加载配置文件,其判断加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。如下代码所示
Wrapper 类同样实现了扩展点接口(Protocol),但是 Wrapper 不是扩展点的真正实现。
通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。
这里先卖个关子,也就是他的IOC流程,我们专注于他AOP的实现。

package com.alibaba.xxx;
 
import org.apache.dubbo.rpc.Protocol;
 
public class XxxProtocolWrapper implements Protocol {
    Protocol impl;
 
    public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
 
    // 接口方法做一个操作后,再调用extension的方法
    public void refer() {
        //... 一些操作
        impl.refer();
        // ... 一些操作
    }
 
    // ...
}

5.如果一个扩展接口有多个包装类,怎么办?比如Protocol这个扩展点,他有两个Wrapper,该怎么生成?

比如截取的这个Protocol,就有两个包装类。


image.png

ExtensionLoader#createExtension方法如下:

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);
            // 创建 Wrapper 拓展对象
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

注意上述代码中的:

 // 创建 Wrapper 拓展对象
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

这里的代码就很好理解了,简单来说就是——套娃。
instance在一开始是我们的InjvmProtocol
cachedWrapperClasses存储了他的所有我们wrapperClasses ,即两个包装类ProtocolFilterWrappeProtocolListenerWrapper
按照For循环取出来的顺序,第一次循环先把InjvmProtocol包装进ProtocolFilterWrappe,第二此循环的时候instance 已经是ProtocolFilterWrappe了,此时再把ProtocolFilterWrappe包装进ProtocolListenerWrapper最后返回,大功告成。

6.那么Dubbo SPI是怎么实现IOC的呢?

createExtension()(用以创建扩展类的方法)时会调用injectExtension()来注入属性

 private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) { // setting && public 方法
                        // 获得属性的类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 获得属性
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 获得属性值
                            Object object = objectFactory.getExtension(pt, property);
                            // 设置属性值
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

如代码所示,在injectExtension方法中,会判断这个类中是否有setter方法,如果有的话会通过ExtensionFactory自动注入对应的扩展点实例。

这个时候就出现问题了:
  • 1.如果扩展接口有多个实现类,具体注入哪个呢?
  • 2.ExtensionFactory是什么,怎么注入的?
    这个时候就需要另一个注解出场了:@Adaptive

@Adaptive

参考链接:
@Adaptive注解
Dubbo SPI之Adaptive详解

  1. 注解在类上时,直接使用被注解的类。也因此,一个拓展,只允许最多注解一个类(可以存在多个类,但是只能注解一个),否则会存在多个会是冲突。
    这里多提一嘴,整个Dubbo源码中用在类上的@Adaptive只有AdaptiveExtensionFactoryAdaptiveCompiler
  2. 注解在方法上时,代表自动生成和编译一个动态的Adpative类,它主要是用于SPI机制,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类.
    例如 Protocol的spi类有 injvm dubbo registry filter listener等等 很多扩展未知类,
    而实现这种机制的入口主要在ExtensionLoader.getAdaptiveExtension()方法
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("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            // 若之前创建报错,则抛出异常 IllegalStateException
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
        return (T) instance;
    }

当缓存不存在时,调用 #createAdaptiveExtension()方法,创建自适应拓展对象,并添加到 cachedAdaptiveInstance 中。

private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

调用#getAdaptiveExtensionClass()方法,获得自适应拓展类。
调用Class#newInstance() 方法,创建自适应拓展对象。
调用 #injectExtension(instance)方法,向创建的自适应拓展对象,注入依赖的属性。

那我们再来看看getAdaptiveExtensionClass()

    private Class<?> getAdaptiveExtensionClass() {
        /**
         * 这边深入下去会用到loadFile(),也就是加载在配置文件里的类,
        如果有AdaptiveClass,会把AdaptiveClass缓存到cachedAdaptiveClass 
         */
        getExtensionClasses();
        /**
         * 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,
        如果不行的话,就得考虑自己进行利用动态代理创建一个了
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 利用动态代理创建一个扩展类
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

先来看看loadFile()做了啥
在loadFile()方法中,会有判断类是否有@Adaptive注解,节选如下

                   /**
                      * 判断这个加载的类上,有没有Adaptive的注解,如果有
                      */
                         if (clazz.isAnnotationPresent(Adaptive.class)) {
                          if (cachedAdaptiveClass == null) {
                         /**
                           * 将此类作为cachedAdaptiveClass
                              */
                               cachedAdaptiveClass = clazz;
                          }
                               /**
                            * 多个adaptive类的实例,报错
                             */
                           else if (!cachedAdaptiveClass.equals(clazz)) {
                 throw new IllegalStateException("More than 1 adaptive class found: "
                                           + cachedAdaptiveClass.getClass().getName()
                                    + ", " + clazz.getClass().getName());
                                  }
                      }
                 /**
                   * 如果这个类,没有Adaptive注解
                     */
                    else {
                      ……
                          }

如果有的类上带有@Adaptive注解,那么将这个类赋值给cachedAdaptiveClass,如果没有,那么在else里面就会进入是否是wapper(包装类)的判定。

也就是说
如果类上没有@Adaptive注解,此时getAdaptiveExtensionClass() 中的
if (cachedAdaptiveClass != null)也为空,那么就要进入createAdaptiveExtensionClass()了。

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();
        return compiler.compile(code, classLoader);
    }

思路很简单,将类以字符串的形式拼接出来,然后利用编译器进行编译,返回编译后的class对象。寻找编译器的过程和具体编译的过程不是我们此次所要关心的,我们关心的是这个createAdaptiveExtensionClassCode方法创建的字符串格式的数据是啥样的,用到了哪些数据。

样例代码如下:

@SPI("dubbo")
public interface AdaptiveExt2 {   
    @Adaptive({"t"})
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?t=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
cloud

编译后代码如下:

package shuqi.dubbotest.d;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class AdaptiveExt2$Adpative implements shuqi.dubbotest.spi.adaptive.AdaptiveExt2 {
    public java.lang.String echo(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("t", "dubbo");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(shuqi.dubbotest.spi.adaptive.AdaptiveExt2) name from url(" + url.toString() + ") use keys([t])");
        shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension = (shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
}

分析:
@Adaptive中,value 是个字符数组,通过该属性从 URL 中获取扩展名,来决定使用哪个扩展。分为几种情况:

  • 1.设置了 value,且从 URL 中找到了对应的扩展名,则使用该扩展;

  • 2.设置了 value,但从 URL 中找不到扩展名,则使用默认的扩展,即 @SPI 中配置的 value(在此例中为dubbo),还是找不到则抛出异常;

  • 3.未设置 value,则根据接口名生成 value,比如接口 YyyInvokerWrapper 生成 value = “yyy.invoker.wrapper”。

注意下面代码最后传入的extName,这个才是从url中获得的value,通过这个字符串从ExtensionLoader找到对应的扩展类,再通过这个扩展类来执行相应的操作,其他无非是一些判空操作。url.getParameter("t", "dubbo");也只是如果url中有key为t对应的value,则返回,否则返回dubbo。

shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension =
(shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class). (extName);

将上面生成的字符串编译成Class对象,作为适配器类,返回,然后实例化后,进行依赖注入需要的属性,随后缓存,备下次使用。

9.为什么源码里只有AdaptiveExtensionFactory和AdaptiveCompiler注解了@Adaptive?

此种情况,表示拓展的加载逻辑已经被确定下来了,一般不再进行除此以外额外的拓展。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成(生成代码后编译来生成新的类)。

10.为什么AdaptiveCompiler这个类是固定已知的?

因为整个框架仅支持Javassist和JdkCompiler。直接固定了对应的实现而不需要动态生成代码实现,就像策略模式直接确定实现类。在扩展点初始化时,如果发现实现类上有@Adaptive注解,会直接赋值给cachedAdaptiveClass,并把实例缓存到cachedAdaptiveInstance中。

他的UML图如下,

image.png
AdaptiveCompiler中只是作为一个管理作用,用来管理其他两个真正的Compiler,他的代码也非常简单。
@Adaptive
public class AdaptiveCompiler implements Compiler {

    /**
     * 默认编辑器的拓展名
     */
    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        // 获得 Compiler 的 ExtensionLoader 对象。
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        // 使用设置的拓展名,获得 Compiler 拓展对象
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        // 获得默认的 Compiler 拓展对象
        } else {
            compiler = loader.getDefaultExtension();
        }
        // 编译类
        return compiler.compile(code, classLoader);
    }

}

setDefaultCompiler(compiler) 静态方法,设置默认编辑器的拓展名( DEFAULT_COMPILER )。该方法被 ApplicationConfig#setCompiler(compiler) 方法调用,代码如下:

 public void setCompiler(String compiler) {
        this.compiler = compiler;
        AdaptiveCompiler.setDefaultCompiler(compiler);
    }

在 <dubbo:application compiler="" /> 配置下,即可触发该方法。
如果没有配置,则使用接口上的@SPI("javassist")来进行

11.为什么AdaptiveExtensionFactory这个类是固定已知的?

同样的,因为整个框架仅支持2个objFactory,一个是spi,另一个是spring


image.png

AdaptiveExtensionFactory与上文的AdaptiveCompiler类似,都是用来管理其他两个扩展类的,在这个AdaptiveExtensionFactory里直接就加载了所有的ExtensionFactory实现并缓存起来,SpingExtensionFactory提供了保存Spring上下文的静态方法,SpiExtensionFactory可以从Dubbo 容器中获取扩展点实例,这样就打通了Spring容器与SPI容器,注入会从这两个容器里找。

AdaptiveExtensionFactory代码如下:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    /**
     * ExtensionFactory 拓展对象集合
     */
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        // 使用 ExtensionLoader 加载拓展对象实现类。
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        // 遍历工厂数组,直到获得到属性
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

12.@Activate

Activate主要用在有多个扩展点实现,需要根据不同条件激活的场景中。
比如Filter需要多个同时激活,因为每个Filter实现的是不同的功能。

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
        // 处理自动激活的拓展对象们
        // 判断不存在配置 `"-name"` 。例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            // 获得拓展实现类数组
            getExtensionClasses();
            // 循环
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) { // 匹配分组
                    // 获得拓展对象
                    T ext = getExtension(name);
                    if (!names.contains(name) // 不包含在自定义配置里。如果包含,会在下面的代码处理。
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name) // 判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
                            && isActive(activate, url)) { // 判断是否激活
                        exts.add(ext);
                    }
                }
            }
            // 排序
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        // 处理自定义配置的拓展对象们。例如在 <dubbo:service filter="demo" /> ,代表需要加入 DemoFilter (这个是笔者自定义的)。
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 判断非移除的
                // 将配置的自定义在自动激活的拓展对象们前面。例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    // 获得拓展对象
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        // 添加到结果集
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

从源码可以看到,主要流程分为四步

  • 1、检查缓存,如果缓存没有,就初始化所有扩展类实现的集合
  • 2、遍历整个@Activate集合,根据URL的匹配条件,得到所有符合激活条件的扩展类实现。然后根据@Activate中配置的before、after、order参数进行排序
  • 3.遍历所有用户自定义扩展类名称,将自定义扩展类放在自动激活的拓展对象们前面
  • 4.返回所有自动激活类集合。

相关文章

  • dubbo的spi机制

    dubbo的spi机制 dubbo的扩展点加载机制源自于java的spi扩展机制。那么,何为java的spi扩展机...

  • dubbo原理:SPI机制(二)

    在上一篇:SPI机制(一)中研究了Dubbo SPI的自适应原理;SPI机制(二)中我们来研究下Dubbo SPI...

  • Dubbo SPI机制分析【二】

    title: Dubbo SPI机制分析【二】tags: Dubbo,SPI,源码grammar_cjkRuby:...

  • Dubbo SPI机制分析【一】

    title: Dubbo SPI机制分析tags: Dubbo,SPI,源码grammar_cjkRuby: tr...

  • dubbo 原理解析

    rpc 流程:1、dubbo spi 中的 warp 机制,实现类增强2、dubbo spi 中的自适应机制,通过...

  • Dubbo剖析-SPI机制

    文章要点 什么是spi dubbo为什么实现自己的spi dubbo的adaptive机制 dubbo的IOC和A...

  • dubbo源码分享2- SPI

    在 Dubbo 中,SPI 贯穿在整个 Dubbo 的核心,所以把 Dubbo 里面用得比较多的 SPI 机制做一...

  • Dubbo第三天

    5. SPI 机制原理 因为dubbo 框架是建立的 SPI 机制上,因此在探寻 dubbo 框架源码前,我们需要...

  • Dubbo SPI的认识

    Dubbo是基于Java原生SPI机制思想的一个改进. 关于JAVA 的SPI机制 SPI全称(service p...

  • JAVA SPI解析

    JAVA SPI解析 在阅读Dubbo源码时发现Dubbo针对java的spi机制做了扩展。那么spi究竟是什么呢...

网友评论

      本文标题:Dubbo的SPI机制

      本文链接:https://www.haomeiwen.com/subject/hwzqcktx.html