4

Dubbo2.7的Dubbo SPI实现原理细节 - 朱季谦

 1 year ago
source link: https://www.cnblogs.com/zhujiqian/p/17094173.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Dubbo2.7的Dubbo SPI实现原理细节

总结/朱季谦

本文主要记录我对Dubbo SPI实现原理的理解,至于什么是SPI,我这里就不像其他博文一样详细地从概念再到Java SPI细细分析了,直接开门见山来分享我对Dubbo SPI的见解。

Dubbo SPI的机制比较类似Spring IOC的getBean()加载,当传入一个存在的beanName,就可以返回该beanName对应的对象。同理,在Dubbo SPI中,我们同样传入一个存在的name,Dubbo框架会自动返回该key对应的对象。不难猜测,Dubbo SPI与Spring IOC在底层应该都有一个大致相似的逻辑,简单的说,就是两者都可通过beanName或key值,到框架里的某个map缓存当中,找到对应的class类名,然后对该class类名进行反射生成对象,初始化完成该对象,最后返回一个完整的对象。然而,在这个过程当中,Spirng相对来说会更复杂,它里面还有一堆后置处理器......

简单举一个例子,大概解释一下Dubbo SPI实现原理,然后再进一步分析源码。

首先,我在org.apache.dubbo.test目录下,定义一个@SPI注解到接口:

package org.apache.dubbo.test;

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

@SPI("dog")
public interface Animal {
    void haveBehavior();
}

然后,在同一个目录下,创建两个实现该接口的类,分别为Dog,Cat。

Dog——

package org.apache.dubbo.test;

public class Dog implements Animal {

    @Override
    public void haveBehavior() {
        System.out.println("狗会叫");
    }
}

Cat——

package org.apache.dubbo.test;

public class Cat implements Animal {
    @Override
    public void haveBehavior() {
        System.out.println("猫会抓老鼠");
    }
}

注意看,Animal接口的类名为org.apache.dubbo.test.Animal,接下来,我们在resource目录的/META_INF/dubbo需新建一个对应到该接口名的File文件,文件名与Animal接口的类名一致:org.apache.dubbo.test.Animal。之所以两者名字要一致,是因为这样只需拿到Animal接口的类名,到resource目录的/META_INF/dubbo,就可以通过该类名,定位到与Animal接口相对应的文件。

在Dubbo中,文件名org.apache.dubbo.test.Animal的文件里,其实存了类似Spring bean那种的数据,即id对应bean class的形式——

cat=org.apache.dubbo.test.Cat
dog=org.apache.dubbo.test.Dog

这两行数据,分别是Animal接口的实现类Cat和Dog的class全限名。

整个的目录结构是这样的——

image

最后写一个测试类DubboSPITestTest演示一下效果——

package org.apache.dubbo.test;

import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;

class DubboSPITestTest {

    @Test
    public void test(){

        Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
        dog.haveBehavior();

        Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
        cat.haveBehavior();

    }
}

执行结果如下——

image

先简单捋一下这个思路是怎么实现的,ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")这行代码内部,会根据Animal接口完整名org.apache.dubbo.test.Animal找到某个指定目录下的同名File文件org.apache.dubbo.test.Animal,然后按行循环解析文件里的内容,以key-value形式加载到某个map缓存里。

类似这样操作(当然,源码里会更复杂些)——

Map<String,String> map = new HashMap<>();
map.put("cat","org.apache.dubbo.test.Cat");
map.put("dog","org.apache.dubbo.test.Dog");

当然,真实源码里,value存的是已根据类名得到的Class,但其实无论是类名还是Class,最后都是可以反射生成对象的,

这时候,就可以根据运行代码去动态获取到该接口对应的实现类了。例如,需要使用的是org.apache.dubbo.test.Cat这个实现类,那么在调用getExtension("cat")方法中,我们传入的参数是"cat",就会从刚刚解析文件缓存的map中,根据map.get("cat")拿到对应org.apache.dubbo.test.Cat。既然能拿到类名了,不就可以通过反射的方式生成该类的对象了吗?当然,生成的对象里可能还有属性需要做注入操作,这就是Dubbo IOC的功能,这块会在源码分析里进一步说明。当对象完成初始化后,就会返回生成的对象指向其接口引用Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")。

整个过程,就是可根据代码去动态获取到某个接口的实现类,方便灵活调用的同时,实现了接口和实现类的解耦。

Dubbo SPI是在Java SPI基础上做了拓展,Java SPI中与接口同名的文件里,并不是key- value的形式,纯粹是按行来直接存放各实现类的类名,例如这样子——

org.apache.dubbo.test.Cat
org.apache.dubbo.test.Dog

这就意味着,Java SPI在实现过程中,通过接口名定位读取到resource中接口同名文件时,是无法做到去选择性地根据某个key值来选择某个接口的实现类,它只能全部读取,再全部循环获取到对应接口实现类调用相应方法。这就意味着,可能存在一些并非需要调用的实现类,也会被加载并生成对象一同返回来,无法做到按需获取。

因此,Dubbo在原有基础上,则补充了Java SPI无法按需通过某个key值去调用指定的接口实现类,例如上面提到的,Dubbo SPI可通过cat这个key,去按需返回对应的org.apache.dubbo.test.Cat类的实现对象。

下面就来分析一下具体实现的原理细节,以下代码做案例。

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();

先来分析ExtensionLoader.getExtensionLoader(Animal.class)方法——

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //判断传进来参数是否空
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    //判断是否为接口
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
  
    //判断是否具有@SPI注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

在这个方法里,若传进来的Class参数为空或者非接口或者没有@SPI注解,都会抛出一个IllegalArgumentException异常,说明传进来的Class必须需要满足非空,为接口,同时具有@SPI注解修饰,才能正常往下执行。我们在这里传进来的是Animal.class,它满足了以上三个条件。

@SPI("cat")
public interface Animal {
    void haveBehavior();
}

接下来,在最后这部分代码里,主要是创建一个ExtensionLoader对象。

ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
return loader;

最后,返回的也是创建的ExtensionLoader对象,该对象包括了两个东西,一个是type,一个是objectFactory。这两个东西在后面源码里都会用到。

image

创建完ExtensionLoader对象后,就会开始调用getExtension方法——

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");

进入到getExtension("cat")方法当中,内部会调用另一个重载方法getExtension(name, true)。

public T getExtension(String name) {
    return getExtension(name, true);
}

让我们来看一下该方法内部实现——

public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    //step 1
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    
    //step 2
    //双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //创建扩展实例
                instance = createExtension(name, wrap);
                //设置实例到holeder中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

该方法主要有两部分。

step 1,先从缓存里查找name为“cat”的对象是否存在,即调用getOrCreateHolder(name),在该方法里,会去cachedInstances缓存里查找。cachedInstances是一个定义为ConcurrentMap<String, Holder


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK