Dubbo内核解析-Spi

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/z15732621582/article/details/81056301

1.dubbo架构原理

    dubbo四大角色关系图

    

2.dubbo的SPI实现

    什么是JDK的SPi(Service Provider Interface)

    相对于api 来说我们都熟悉,但是spi知道的较少一些,因为我们大多是使用api,参与开源项目较少,spi是给拓展者使用的,对于一个好的开源框架来说,有必要留一些拓展点让参与者尽量黑盒拓展,而不是白盒修改代码,否则的话框架作者能做的功能,拓展者也在做的话,那么分支、合并将很难管理。简单来说就是为某个接口寻找服务实现的机制。

    从使用的层面来看的话,就是动态的给接口添加实现类,有点像IOC的思想,将装配的控制权向外转移。比如我们在浏览器安装插件,而不是拆了重新组装。

    spi简单实现

    接口和具体实现类

package com.mcgj.spi;

/**
 * Created by zjc on 2018/7/15.
 */
public interface ILookTheWorldCup {
    void look();
}
package com.mcgj.spi.impl;

import com.mcgj.spi.ILookTheWorldCup;

/**
 * Created by zjc on 2018/7/15.
 */
public class LookCroatiaImpl  implements ILookTheWorldCup {
    @Override
    public void look() {
        System.out.println("克罗地亚");
    }
}
import com.mcgj.spi.ILookTheWorldCup;

/**
 * Created by zjc on 2018/7/15.
 */
public class LookFranceImpl implements ILookTheWorldCup {
    @Override
    public void look() {
        System.out.println("法国");
    }
}

新建个配置文件,将实现类的全限定名粘贴,并把配置文件放到META-INF/services/接口全限定名 (为什么是这个路径下?约定--->这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了;META-INF/dubbo/   META-INF/services/

com.mcgj.spi.impl.LookCroatiaImpl
com.mcgj.spi.impl.LookFranceImpl

目录结构:

                                      

    测试结果:


package com.mcgj;

import com.mcgj.spi.ILookTheWorldCup;
import org.junit.Test;

import java.util.ServiceLoader;

/**
 * Created by zjc on 2018/7/15.
 */
public class TestSpi {
    @Test
    public void test() throws Exception{
        ServiceLoader<ILookTheWorldCup> loaders=ServiceLoader.load(ILookTheWorldCup.class);
        for (ILookTheWorldCup lookTheWorldCup:loaders){
            lookTheWorldCup.look();
        }
    }
}

小结:通过配置文件就能动态的改变一个接口的实现类。但是想增加一个实现类LookBelgiumImpl,

不仅仅要改配置文件,还要添加这个实现类才行。这就接下来需要学习动态字节码技术,(javassist)可以再运行时动态生成Java类,不存在要预先把接口的实现类先写好。

    我们以Protocol接口为例, 接口上打上SPI注解,默认扩展点名字为dubbo

@SPI("dubbo")

public interface Protocol{

}

 

                               

Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo

                            

分析可得出:

①JDK的spi使用的是for循环通过if判断过去指定的spi对象,而dubbo使用key-value形式获取;

@SPI("javassist")
public interface Compiler {
    //省略...
}
public Class<?> compile(String code, ClassLoader classLoader) {
    Compiler compiler;
    ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
    String name = DEFAULT_COMPILER; // copy reference
    if (name != null && name.length() > 0) {
        compiler = loader.getExtension(name);  //通过key的name获取配置文件中的编译方式
    } else {
        compiler = loader.getDefaultExtension();
    }
    return compiler.compile(code, classLoader);
}
//com.alibaba.dubbo.common.compiler.Compiler 文件配置如下
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

②JDK的spi不支持默认值,dubbo增加了默认值的设计

@SPI("dubbo")//@SPI("dubbo")代表默认的spi对象,比如Protocol默认使用的是dubbo,可通过
public interface Protocol{
}


       其实这个功能不用spi,使用spring的ioc也可以通过配置文件来实现动态注入不同的实现类啊,但是我们在从dubbo的官网可以看到这句话:

理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略

     Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。

    Dubbo改进了JDK标准的SPI的以下问题:

    1.JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

    2.如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

    3.增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

①提升性能 :最容易想到的就是缓存   ,可以说对于应答提升性能有关问题的,往缓存答可肯定没问题,下面通过源码一步步找到缓存所在!

    源码的扩展点框架主要在  com.alibaba.dubbo.common.extension

    结构如下:     

com.alibaba.dubbo.common.extension
 |
 |--factory
 |     |--AdaptiveExtensionFactory   #稍后解释
 |     |--SpiExtensionFactory        #稍后解释
 |
 |--support
 |     |--ActivateComparator
 |
 |--Activate  #自动激活加载扩展的注解
 |--Adaptive  #自适应扩展点的注解
 |--ExtensionFactory  #扩展点对象生成工厂接口
 |--ExtensionLoader   #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类
 |--SPI   #扩展点注解

ExtensionLoader没有提供public的构造方法,但是提供了一个public staticgetExtensionLoader,这个方法就是获取ExtensionLoader实例的工厂方法。其public成员方法中有三个比较重要的方法:

  • getActivateExtension :根据条件获取当前扩展可自动激活的实现
  • getExtension : 根据名称获取当前扩展的指定实现
  • getAdaptiveExtension : 获取当前扩展的自适应实现
        这三个方法将会是我们重点关注的方法;* 每一个ExtensionLoader实例仅负责加载特定SPI扩展的实现*。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader实例,下面我们就来看一下获取ExtensionLoader实例的工厂方法getExtensionLoader
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 interface!");
    }
    if(!withExtensionAnnotation(type)) { // 只接受使用@SPI注解注释的接口类型,否则抛异常
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    // 先从静态缓存 EXTENSION_LOADERS中获取对应的ExtensionLoader实例
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 为Extension类型创建ExtensionLoader实例,并放入静态缓存
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

   可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。

   ExtensionLoader的私有构造函数:

private ExtensionLoader(Class<?> type) {
    this.type = type;

    // 如果扩展类型是ExtensionFactory,那么则设置为null
    // 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类)
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

      保存对应的扩展类型,并且设置了一个额外的objectFactory属性,它是一个ExtensionFactory类型,ExtensionFactory用于加载扩展的实现:

@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     * 
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}
        我们看到@SPI注解在此接口上,说明他也是一个扩展点,从上述的结构图中可以看出dubbo内部提供了两个实现类SpiExtensionFactory 和 AdaptiveExtensionFactory,ExtensionLoader的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory是,object字段被设置为null。由于ExtensionLoader的使用范围有限(基本上局限在ExtensionLoader中),因此对他做了特殊对待:在需要使用ExtensionFactory的地方,都是通过对应的自适应实现来代替。


      ExtensionFactory实现类中,AdaptiveExtensionFactotry@Adaptive注解注释,也就是它就是ExtensionFactory对应的自适应    扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory调用的地方,实际上调用的都是AdpativeExtensionFactory 

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        // 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回
        // 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

        看到这里,明白了这货相当于入口,遍历当前系统中所有的ExtensionFactory实现来获取指定的扩展实现,获取到扩展实现或遍历完所有的ExtensionFactory实现。这里调用了ExtensionLoadergetSupportedExtensions方法来获取ExtensionFactory的所有实现,又回到了ExtensionLoader类。

        现在我们在看看像刚才提到的那三个比较重要的方法 具体调用流程:

    

getExtension


getExtension(name)
    -> createExtension(name) #如果无缓存则创建
        -> getExtensionClasses().get(name) #获取name对应的扩展类型
        -> 实例化扩展类
        -> injectExtension(instance) # 扩展点注入
        -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入 

getAdaptiveExtension

public T getAdaptiveExtension()
    -> createAdaptiveExtension() #如果无缓存则创建
        -> getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass
            -> getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive
            -> createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类
                -> createAdaptiveExtensionClassCode() #动态生成实现类java代码
                -> compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化
        -> injectExtension(instance)

getActivateExtesion 
该方法有多个重载方法,不过最终都是调用了三个参数的那一个重载形式。其代码结构也相对剪短,就不需要在列出概要流程了。


public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {  // 判断是否是获取默认实现
        return getDefaultExtension();
    }
    Holder<Object> holder = cachedInstances.get(name);// 缓存
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);// 没有缓存实例则创建
                holder.set(instance);// 缓存起来
            }
        }
    }
    return (T) instance;
}

    这方法中也使用了在调用getExtensionClasses方法的时候收集并缓存的数据,其中涉及到名字和具体实现类型对应关系的缓存属性是cachedClasses

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name); // getExtensionClass内部使用cachedClasses缓存
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz); // 从已创建Extension实例缓存中获取
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance); // 属性注入

        // Wrapper类型进行包装,层层包裹
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            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);
    }
}

        怎么处理缓存的呢?魔鬼都藏在细节当中!

public class Holder<T> {
    
    private volatile T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }

        这个类用于保存一个值,并且给值添加volatile来保证线程的可见性.

②dubbo的spi中增加了IoC、AOP

    首先先了解Spring的IOC,在学习dubbo又是如何实现的?(static修饰方法,是属于类级别的,优先级高,内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,dubbo对扩展实现中公有的set方法且入参个数为一个的方法,

    尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。

 

    dubbo内部默认实现的对象工厂是SpiExtensionFactory和SrpingExtensionFactory,

    他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,

  所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。

2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

 

下面 给出整体活动图



展开阅读全文

没有更多推荐了,返回首页