dubbo3.2- spi机制完全解析

本文全部代码均已上传github,源码地址为:
https://github.com/xixingya/dubbo-demo

第一章 spi 基础

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

1.1 java spi

一句话来概括我们的java spi,Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

1.1.1 概念

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。

1.1.2 使用场景

概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
比较常见的例子:

  • 数据库驱动加载接口实现类的加载

JDBC加载不同类型数据库的驱动

  • 日志门面接口实现类加载

SLF4J加载不同提供商的日志实现类

  • Spring

Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

  • Dubbo

Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口

1.1.3 show code

首先我们来看java spi的机制:话不多说,我们直接看代码

public interface Log {

    void log(String log);

    void logAdaptive(URL url,String log);
}

public class Log4j implements Log {

    @Override
    public void log(String log) {
        System.out.println("log4j print " + log + " this is " + this);
    }

    @Override
    public void logAdaptive(URL url, String log) {
        System.out.println("logAdaptive log4j print " + log + " this is " + this);
    }
}

public class Logback implements Log {

    @Override
    public void log(String log) {
        System.out.println("Logback print " + log +" this is "+ this);
    }

    @Override
    public void logAdaptive(URL url,String log) {
        System.out.println("logAdaptive Logback print " + log +" this is "+ this);
    }
}


image.png

我们运行TestSpi,得到以下结果:

public class TestSpi {
    public static void main(String[] args) {
        // ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        for (int i = 0; i < 3; i++) {
            ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
            for (Log next : serviceLoader) {
                next.log("hello world");
            }
        }

    }
}

/**
log4j print hello world this is tech.xixing.dubbo.demo.spi.java.Log4j@4c873330
Logback print hello world this is tech.xixing.dubbo.demo.spi.java.Logback@119d7047
log4j print hello world this is tech.xixing.dubbo.demo.spi.java.Log4j@776ec8df
Logback print hello world this is tech.xixing.dubbo.demo.spi.java.Logback@4eec7777
log4j print hello world this is tech.xixing.dubbo.demo.spi.java.Log4j@3b07d329
Logback print hello world this is tech.xixing.dubbo.demo.spi.java.Logback@41629346
**/

我们对比最后的数字,可以发现每次获取到的对象都是新的。说明java spi每次获取都是重新去new出来的对象。没有缓存。

1.1.4 深入源码

下面我们看源码,看是否是这样子的:
可以看到ServiceLoader中有个这个类,java.util.ServiceLoader.LazyIterator,我们在初始化的时候,会初始化这个类,这个类实现了Iterator接口。
在每次for调用的时候,会调用next(),而next,会先判断hasNextService,在hasNextService中,就会去判断config,这个config就是从resource中获取的。
即:for (Log next : serviceLoader) 这个代码,next变量是从next()方法获取的。
所以我们获取到了这个实例的代码是在

private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        // 等待被加载的类名。
        Iterator<String> pending = null;
        // 下一个被加载的类名。
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // 这里读取文件获取的
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

1.2 dubbo spi

我们借用官网的话语来解答dubbo spi是什么

什么是可扩展性

可扩展性是一种设计理念,代表了我们对未来的一种预想,我们希望在现有的架构或设计基础上,当未来某些方面发生变化的时候,我们能够以最小的改动来适应这种变化。

可扩展性的优点

可扩展性的优点主要表现模块之间解耦,它符合开闭原则,对扩展开放,对修改关闭。当系统增加新功能时,不需要对现有系统的结构和代码进行修改,仅仅新增一个扩展即可。

扩展实现方式

一般来说,系统会采用 Factory、IoC、OSGI 等方式管理扩展(插件)生命周期。考虑到 Dubbo 的适用面,不想强依赖 Spring 等 IoC 容器。 而自己造一个小的 IoC 容器,也觉得有点过度设计,所以选择最简单的 Factory 方式管理扩展(插件)。在 Dubbo 中,所有内部实现和第三方实现都是平等的。

Dubbo 中的可扩展性
  • 平等对待第三方的实现。在 Dubbo 中,所有内部实现和第三方实现都是平等的,用户可以基于自身业务需求,替换 Dubbo 提供的原生实现。
  • 每个扩展点只封装一个变化因子,最大化复用。每个扩展点的实现者,往往都只是关心一件事。如果用户有需求需要进行扩展,那么只需要对其关注的扩展点进行扩展就好,极大的减少用户的工作量。

Dubbo 中的扩展能力是从 JDK 标准的 SPI 扩展点发现机制加强而来,它改进了 JDK 标准的 SPI 以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

下面带大家实现一个简单的dubbo spi的demo

@SPI("logback")
public interface Log {

    void log(String log);

    @Adaptive(value = "log")
    void logAdaptive(URL url,String log);
}

public class Logback implements Log {

    @Override
    public void log(String log) {
        System.out.println("Logback print " + log +" this is "+ this);
    }

    @Override
    public void logAdaptive(URL url,String log) {
        System.out.println("logAdaptive Logback print " + log +" this is "+ this);
    }
}

public class Log4j implements Log {

    @Override
    public void log(String log) {
        System.out.println("log4j print " + log + " this is " + this);
    }

    @Override
    public void logAdaptive(URL url, String log) {
        System.out.println("logAdaptive log4j print " + log + " this is " + this);
    }
}

public class TestSpiDubbo {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
            extensionLoader.getExtension("logback").log("hello world");
            //extensionLoader.getExtension("log4j").log("hello world");
            extensionLoader.getExtension("log4j").log("hello world");
            //extensionLoader.getExtension("mylog").log("hello world");
        }
    }
}

在resource目录下建立接口全类名的文件:
image.png

第二章 dubbo spi全功能解析

在dubbo spi中存在以下功能点:
用户能够基于 Dubbo 提供的扩展能力,很方便基于自身需求扩展其他协议、过滤器、路由等。下面介绍下 Dubbo 扩展能力的特性。

  • 按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用扩展类实例化,减少资源浪费。
  • 增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能。
  • 增加扩展类的 AOP 能力。Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。
  • 具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。
  • 可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。
  • 提供扩展点的 Adaptive 能力。该能力可以使的一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。也可以调整拓展的生效顺序等等。

从 Dubbo 扩展的设计目标可以看出,Dubbo 实现的一些例如动态选择扩展实现、IOC、AOP 等特性,能够为用户提供非常灵活的扩展能力。

2.1 defaultExtension @SPI

下面我们来进行探讨,dubbo spi获取默认的ext的时候,进行了哪些步骤。
我们写入以下代码:

@Test
void testDefaultExt(){
    for (int i = 0; i < 3; i++) {
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        extensionLoader.getDefaultExtension().log("hello world");
    }
}

我们发现,获取到的loader会去加载默认写到@SPI的注解上的value值。
我们简单看下是如何从@SPI中获取默认的ext的。

private void cacheDefaultExtensionName() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if (names.length > 1) {
                throw new IllegalStateException(
                    "More than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(
                        names));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

上述代码cachedDefaultName,会在获取默认的loader的时候使用。
ok,如果想要具体看代码分析,可以看第三章的代码分析章节对应的代码分析。

2.2 Adaptive

接下来我们来介绍一下我们的重头戏,也是我们dubbo中最广泛使用的Adaptive。
看名字我们也能大概猜出一部分它的含义,Adaptive,即为自适应。那么什么叫自适应呢。dubbo是如何使用的呢。
下面我们先来看看一个例子,结合例子来看如何实现的:

在方法上

@Adaptive标注一般标注在接口的方法上,也可以标注在类,枚举类,方法上,但是比较少见,@Adaptive标注在接口方法上时,要求接口方法入参中有一个URL,这个URL类是org.apache.dubbo.common.URL**,**注解的功能是可以根据方法中URL的入参,来选择对哪一个实现进行调用,使用如下:

  • 首先给interface加上入参,然后给接口方法加上@adaptive注解,设置value的默认值是“log”
@SPI("logback")
public interface Log {

    void log(String log);

    @Adaptive(value = "log")
    void logAdaptive(URL url,String log);
}

然后我们看main函数

public static void main(String[] args) {
    ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
    Log adaptiveExtension = extensionLoader.getAdaptiveExtension();
    URL url = URL.valueOf("dubbo://127.0.0.1:20880?log=logback");
    adaptiveExtension.logAdaptive(url,"hello world");
    url = URL.valueOf("dubbo://127.0.0.1:20880?log=log4j");
    adaptiveExtension.logAdaptive(url,"hello world");
}

跑完结果如下:
image.png
那我们明白了它的第一种用法。adaptive,即通过字段名称来调用不同的实现类的方法。

在类上

事实上,如果作用在类上,那么其实就没有什么特殊的逻辑了,即作用在类上代表着,这个接口获取的Adaptive类都会是你标注的那个类。即,你自己会在标注Adaptive的类上去实现自己的转发策略。
请看例子:

@SPI
public interface Service {

    void sayHello(URL url);
}
@Adaptive
public class CommonService implements Service {
    
    @Override
    public void sayHello(URL url) {
        ExtensionLoader<Service> extensionLoader = ExtensionLoader.getExtensionLoader(Service.class);
        if (url.getParameter("service").equals("common2")) {
            Service common2 = extensionLoader.getExtension("common2");
            common2.sayHello(url);
        } else {
            Service common3 = extensionLoader.getExtension("common3");
            common3.sayHello(url);
        }
        System.out.println("hello CommonService");
    }
}

public class Common2Service implements Service {
    @Override
    public void sayHello(URL url) {
        System.out.println("hello Common2Service");
    }
}

public class Common3Service implements Service{

    @Override
    public void sayHello(URL url) {
        System.out.println("hello Common3Service");
    }
}

    @Test
    public void test2() {
        ExtensionLoader<Service> extensionLoader = ExtensionLoader.getExtensionLoader(Service.class);
        Service adaptiveExtension = extensionLoader.getAdaptiveExtension();
        URL url = URL.valueOf("dubbo://127.0.0.1:20880?service=common3");
        adaptiveExtension.sayHello(url);
    }

通过上述例子,你应该可以理解,Adaptive在类上注解是什么意思了吧。这里commonService代理了Common2和Common3,通过写自己的逻辑,去手动调用common2或者common3的逻辑。

2.3 自动注入

自动注入,相信大家搞java的都用过spring的自动注入,一个注解,或者通过构造器、set方法自动注入。
OK,下面我们来讲dubbo spi的这一个特性,dubbo spi同样支持对Adaptive的类进行自动注入。下面我们看一个例子:

@Activate(group = "my",order = 1)
public class MyLog implements Log {

    private Service service;

    public void setService(Service service) {
        this.service = service;
    }

    @Override
    public void log(String log) {
        System.out.println("my log:"+log);
        URL url = URL.valueOf("test://localhost/test?service=common");
        service.sayHello(url);
    }

    @Override
    public void logAdaptive(URL url, String log) {
        System.out.println("my log logAdaptive:"+log);
    }
}

// 测试用例
public static void main(String[] args) {
        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        List<Log> activateExtensions = extensionLoader.getActivateExtension(url, new String[]{}, "my");
        for (Log activateExtension : activateExtensions) {
            activateExtension.log("hello world");
        }
    }

之前我们把这个Service类的Adaptive给了CommonService,所以,我们这里加上set方法,我们可以在另外一个被SPI托管的类中直接使用这个类。这有点类似于我们spring中的set方法注入。
我们也可以通过debug看下是不是这样子的。
image.png
可以看到确实是这样子的。大家也可以去尝试一下,相关代码都会放在github上。

ok。这里我们给大家一个思考,如果dubbo中,Service接口只有CommonService这一个实现类,那这个注入还能成功吗?为什么?

2.3 Wrapper

ok,接下来我们来讲一下dubbo spi的另一个重头戏,wrapper机制。听到wrapper机制,想必大家都知道一种设计模型是装饰器模式吧。
同样的,如果我们需要对某个spi接口进行增强,我们就可以通过wrapper机制对其进行一个包装,比如说做一些日志啊。打点啊等等。
ok, show code:

public class LogWrapper implements Log {

    private Log logger;

    public LogWrapper(Log log) {
        this.logger = log;
    }

    @Override
    public void log(String log) {
        System.out.println("fdsfsdfdsfs");
        logger.log(log+"wrapper");
        System.out.println("aaa");
    }

    @Override
    public void logAdaptive(URL url, String log) {
        logger.log(log+"wrapper");
    }
}

public class WrapperTest {

    public static void main(String[] args) {
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        Log logback = extensionLoader.getExtension("logback");
        logback.log("hello world");
        Log log4j = extensionLoader.getExtension("log4j");
        log4j.log("hello world");
    }
}

2.5 Activate

第一个字段 group

在dubbo中,这个注解用的最多的地方就是在各种filter中,使用该注解,定义该注解的作用范围是consumer还是provider

下面我们来看一个例子:

public static void main(String[] args) {
    URL url = URL.valueOf("dubbo://127.0.0.1:20880");
    ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
    List<Log> activateExtensions = extensionLoader.getActivateExtension(url, new String[]{}, "my");
    for (Log activateExtension : activateExtensions) {
        activateExtension.log("hello world");
    }
}


第二个字段 order

order用来在获取到的类中,进行排序。大家可以修改一下order,再启动一个main方法,可以看出不一样的顺序来。下面我们看一个例子。我把mylog、logback、log4j的order分别改成了1,3,6。
下面我们看main函数执行后他们的顺序:

public static void main(String[] args) {
        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        List<Log> activateExtensions = extensionLoader.getActivateExtension(url, new String[]{},"my");
        for (Log activateExtension : activateExtensions) {
            activateExtension.log("hello world");
        }
    }

image.png
可以看出,order越小,其越先执行。读者们也可自行实验。

第三章 dubbo spi全代码解析(未完待续)

3.1 @SPI

下面我们来进行探讨,dubbo spi获取默认的ext的时候,进行了哪些步骤。
我们写入以下代码:

@Test
void testDefaultExt(){
    for (int i = 0; i < 3; i++) {
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        extensionLoader.getDefaultExtension().log("hello world");
    }
}

首先入口:

public T getDefaultExtension() {
	getExtensionClasses();
	if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
		return null;
	}
	return getExtension(cachedDefaultName);
}

可以看到,是获取cachedDefaultName,然后就调用通用的getExtension了。
那么cachedDefaultName是怎么赋值的呢?
我们接下来看代码:
首先我们进getExtensionClasses,也就是刚刚代码的第二行。
然后getExtensionClasses里面有调用loadExtensionClasses,
再进入loadExtensionClasses里面,则可以看到,cacheDefaultExtensionName这个方法中,通过spi的注解获取到了默认的名称。
下面我们看它是怎么实现的:

private void cacheDefaultExtensionName() {
    // 从interface中获取SPI这个注解。
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation == null) {
        return;
    }

    String value = defaultAnnotation.value();
    if ((value = value.trim()).length() > 0) {
        String[] names = NAME_SEPARATOR.split(value);
        // 不支持多个default name
        if (names.length > 1) {
            throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                                            + ": " + Arrays.toString(names));
        }
        if (names.length == 1) {
            cachedDefaultName = names[0];
        }
    }
}

接下来我们继续我们的代码之旅,我们现在cachedDefaultName已经变成了SPI中的value。
我们回到上一层:image.png
ok,我们直接看这个strategies,这个是dubbo的spi的加载策略

strategies

我们首先看,strategies是在哪里加载的呢,仔细一看,可以发现:

进入方法看:

该方法有两个值得注意的地方,一个是load方法,一个是sorted。
首先我们看load方法,该方法实际上是通过java spi的机制去加载所有的策略。
那么,所有加载的类我们可以在resource下面找到,即以下三个类。

那么我们首先看下接口

发现接口继承了这个Prioritized,而Prioritized这个接口继承了Comparable,并且实现了compareTo方法,那么我们知道了sorted的方法的作用,是在load完之后排序。
通过compareTo方法我们可以看出是通过getPriority方法来的,getPriority越大则排在前面,即先加载,我们看各个实现类的getPriority方法,可以得出以下加载顺序:

目录是否可以被后面覆盖加载顺序
META-INF/dubbo/internal/不能被覆盖最先加载
META-INF/dubbo/可以被覆盖第二加载
META-INF/services/可以被覆盖最后加载

ok,我们回到之前的代码,我们遍历调用strategies里面,
image.png
这里,有loadDirectory方法,这里是真正去获取文件然后加载解析的逻辑。

ok,进去loadDirectory后,我们直捣黄龙,看loadDirectoryInternal方法。
跳过获取文件解析文件的繁琐逻辑,我们看核心代码:
image.png
可以看到调用了loadFromClass
image.png
我们接着看loadResource:
image.png
接着进入loadClass加载class。ok,loadClass就是具体对代码进行加载了,比如说有没有被@Adaptive注解啊,是不是Wrapper类啊,或者是其他的等等。
image.png

Adaptive 源码

下面我们具体来看源码,实际上,这个源码并不是很复杂:我们把断点打到org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass

下面是完整的调用链路:





这时候,我们把code拿出来一看,就明白了它的生成逻辑。

package com.yupaopao.dubbo.demo.spi.java;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Log$Adaptive implements com.yupaopao.dubbo.demo.spi.java.Log {
    public void log(java.lang.String arg0) {
        throw new UnsupportedOperationException("The method public abstract void com.yupaopao.dubbo.demo.spi.java.Log.log(java.lang.String) of interface com.yupaopao.dubbo.demo.spi.java.Log is not adaptive method!");
    }

    public void logAdaptive(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("log", "logback");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.yupaopao.dubbo.demo.spi.java.Log) name from url (" + url.toString() + ") use keys([log])");
        com.yupaopao.dubbo.demo.spi.java.Log extension = (com.yupaopao.dubbo.demo.spi.java.Log) ExtensionLoader.getExtensionLoader(com.yupaopao.dubbo.demo.spi.java.Log.class).getExtension(extName);
        extension.logAdaptive(arg0, arg1);
    }
}

这时候我们接着讲下面的逻辑:
我们看到上面有个编译类,org.apache.dubbo.common.compiler.Compiler,这个Compiler也是通过getAdaptiveExtension来获取的,那么,正好,它是通过在类上使用@Adaptive 来使用的,接下来我们把断点打点loadClass下,根据调用堆栈,可以很清楚的看出,在类上面使用@Adaptive那么,在getAdaptiveExtension不会生成类,而是直接返回这个被Adaptive注解的类实例。
在类上面使用注解

ok, Compiler 这个类我们就不去追究咋实现的了,@Adaptive这个讲得差不多了,接下来我们看:

Wrapper机制实现源码

ok,我们在resources下面的对应文件中加上这一行
wrapper=tech.xixing.dubbo.demo.spi.wrapper.LogWrapper
image.png
接着我们把断点卡在这里:
image.png
ok,debug 启动!
找到代码中adaptive的测试方法或者自己写一个即可。

public static void main(String[] args) {
        ExtensionLoader<Log> extensionLoader = ExtensionLoader.getExtensionLoader(Log.class);
        Log adaptiveExtension = extensionLoader.getAdaptiveExtension();
        URL url = URL.valueOf("dubbo://127.0.0.1:20880?log=logback");
        adaptiveExtension.logAdaptive(url, "hello world");
        url = URL.valueOf("dubbo://127.0.0.1:20880?log=log4j");
        adaptiveExtension.logAdaptive(url, "hello world");
    }

接下来我们看堆栈:
从最底层开始看起
第一层:可以看到,卡在了adaptiveExtension的logAdaptive方法上。
image.png

第二层:是个代理类,我们上次已经说过了,adaptive生成代理类的逻辑。
image.png
第三层:ok,这时候,到我们核心逻辑里了:
image.png
接着我们进去方法,看第四层:
这里调用了createExtension方法。我们即将进这个方法去看看里面到底咋实现的。
image.png
但是笔者想再问大家一个问题,上图出现了一个Holder类,这里holder这个类是用来干嘛的呢。在这里我把holder这个类给各位贴出来。各位有兴趣的可以思考一下holder类的作用好吧。

public class Holder<T> {

    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

ok,我们接下来往上找堆栈,第五层,可以看到,到了一个wrapperClass,
image.png
wrapperClass.getConstructor(type).newInstance(instance))。
wrapperClass值是我们刚刚写的那个class
image.png
ok,wrapperclass是怎么赋值的呢。我们往上找找:
image.png
我们接下来找,cachedWrapperClasses是在哪里赋值的。
image.png
好的,我们找到了!
image.png
那这个loadClass是 哪里调用的呢。我们回望之前看SPI源码的那里最后,方法走到了这里。
所以初始化的时候,其实就调用了这个方法把Wrapper类当成了包装类。
那怎么判断是不是Wrapper类的呢,其实很简单:
image.png
判断构造方法,是不是存在传入一个是自己接口类的构造方法。
好了,Wrapper机制我们讲完啦,大家也可以跟着文中的路径自行debug一下哦,相信你会有所收获的!

Activate全代码解析

activate的机制也是类似的,读者可以一样看loadClass里面最后else部分,这是缓存activate的逻辑。获取逻辑在getActivateExtension里面。篇幅限制。这个就留给读者自行阅读了。

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值