Dubbo内核之SPI机制

一、JDK的SPI机制

SPI 的全名为 Service Provider Interface,java设计出SPI目的是为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。这样程序运行的时候,该机制就会为某个接口寻找服务的实现,有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。例如,JDBC驱动,可以加载MySQL、Oracle、或者SQL Server等,目前有不少框架用它来做服务的扩张发现。

通过一张图来看看,用SPI需要遵循哪些规范,因为SPI毕竟是JDK的一种标准:

JAVA SPI

SPI约定:

  • 在工程的META-INF/services/目录下,以接口的全限定名作为文件名,文件内容为实现接口的服务类;
  • 使用ServiceLoader动态加载META-INF/services下的实现类;
  • 接口的实现类需含无参构造函数;(因为类默认包含无参构造函数,如果我们没有重载构造函数所以此处可忽略)

如果在META-INF/services下有接口实现类,存在多个(例如jar包下面也有相应),系统如何处理?Jdk会全部加载,java.util.ServiceLoader在加载资源文件时,已经考虑了这个问题。譬如Mysql的驱动包就包含两个驱动:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

mysql中驱动包的具体实现:

java中的ServiceLoader就会根据该路径去加载装配模块。但是JDK的SPI实现有着以下缺点:

  • 获取实现类的方式只能通过Iterator遍历,不能通过具体参数来获取。
  • 接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。

二、DUBBO-SP机制

dubbo spi的目的也是一样:可以获取类的实例对象,和java的SPI机制非常相似,但是又增加了如下功能:

  • 可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

约定:
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。 (注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)

扩展Dubbo的协议示例:
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

xxx=com.alibaba.xxx.XxxProtocol

实现内容为:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;
public class XxxProtocol implemenets Protocol {

    // ...
}

1.dubbo源码分析

dubbo的扩展点框架主要位于这个包下:com.alibaba.dubbo.common.extension
大概结构如下:

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

其中最核心的类就是ExtensionLoader,几乎所有特性都在这个类中实现,先来看下他的结构:
ExtensionLoader没有提供public的构造方法,但是提供了一个public static的getExtensionLoader,这个方法就是获取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!");
    }

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

该方法需要一个Class类型的参数,该参数表示希望加载的扩展点类型,该参数必须是接口,且该接口必须被@SPI注解注释,否则拒绝处理。检查通过之后首先会检查ExtensionLoader缓存中是否已经存在该扩展对应的ExtensionLoader,如果有则直接返回,否则创建一个新的ExtensionLoader负责加载该扩展实现,同时将其缓存起来。可以看到对于每一个扩展,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);

}

同时ExtensionFactory也被@SPI注解注释,说明他也是一个扩展点,从前面com.alibaba.dubbo.common.extension包的结构图中可以看到,dubbo内部提供了两个实现类:SpiExtensionFactoryAdaptiveExtensionFactory,实际上还有一个SpringExtensionFactory,不同的实现可以已不同的方式来完成扩展点实现的加载,这块稍后再来学习。从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;
    }

}

扩展点加载机制(ExtensionLoader)
dubbo 大白话系列-扩展点机制
dubbo之SPI解析