先来了解一下java中的spi机制

jdk中的spi

SPI简介

一种策略模式,通过在META-INF/services/包下定义接口命名的文件,来决定使用哪个实现

怎么看dubbo的源码_怎么看dubbo的源码


调用过程

public class Main {

    public static void main(String[] args) {
        System.out.println("---加载接口--");
        ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<SpiService > iterator = serviceLoader.iterator();
        //上面只声明了两个接口,所以这里会分别调用rmi和rest的实现类的sayhello方法
        while (iterator.hasNext()) {
            SpiService s1= iterator.next();
            s1.sayHello
        }
        System.out.println("---运行结束---");
    }
}

spi 全称为(Service Provider Interface),是JDK内置的一种服务提供机制。
这个是针对厂商或者插件的。
一般来说对于未知的实现或者对扩展开放的系统,通常会把一些东西抽象出来,抽象的各个模块往往有很多不同的实现方案,例如:日志模块、xml解析模块、jdbc模块等。
SPI约定
当服务的提供者,提供了服务接口的一种实现之后,在jar包中META-INF/services目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类(全称)。
而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

在日志实现中可以使用

dubbo中的spi实现

dubbo中的spi实现更加人性化,可以支持自定义标签中的指定属性决定实现类的读取

比如这个如在均衡的策略

怎么看dubbo的源码_dubbo源码_02

dubbo中的api的使用流程

凡是dubbo中, 接口上有 @SPI标注的,都表明此接口支持扩展,能以标签中属性的方式进行配置扩展
类似这个负载的接口

@SPI("random")
public interface LoadBalance {
    @Adaptive({"loadbalance"})
    <T> Invoker<T> select(List<Invoker<T>> var1, URL var2, Invocation var3) throws RpcException;
}

下面我们自己来写一个负载的扩展类
1、引入jar依赖

<!-- dubbo支持 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.7</version>
            <scope>provided</scope>
        </dependency>

2、编写实现类

怎么看dubbo的源码_dubbo_03


3、resource下新建类全路径名文件,为每个实现分配一个key

怎么看dubbo的源码_加载器_04


4、在消费端,便可使用上面步骤中定的实现策略(以key指代)

怎么看dubbo的源码_怎么看dubbo的源码_05

dubbo中SPI机制源码解读

在运行的时候会通过一个叫做ExtensionLoader的加载器来进行dubbo的扩展点加载
拿消费端的Reference标签举例,
通过看dubbo解析自定义标签的源码

this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));

我们知道dubbo是将reference标签的内容封装到ReferenceBean里的,打开ReferenceBean

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {}

打开父类ReferenceConfig,可以看到创建代理对象时的这一句核心代码

this.invoker = refprotocol.refer(this.interfaceClass, (URL)this.urls.get(0));

而这个refprotocol则是个ReferenceConfig的静态变量
(也许您会好奇,每一个reference标签对应一个ReferenceConfig对象,那在这里的协议使用类变量,会不会导致每个对象只有一个协议呢?这里就牵扯到dubbo的ExtensionLoader加载器了)

//getExtensionLoader获取加载器   getAdaptiveExtension获取代理类
 private static final Protocol refprotocol = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

getExtensionLoader(Protocol.class)为protocol接口生成一个加载器
getAdaptiveExtension(),使用加载器生成一个代理对象---- protocol接口对象
代理对象执行时,根据参数(扩展名extName)选择实际对象

最后的效果:
每个接口扩展点----- 对应一个ExtensionLoader加载器,内部是一个currenthashmap 如:
protocol -------------- ExtensionLoader实例< protocol>
filter -------------- ExtensionLoader实例< filter >
loadbalance -------------- ExtensionLoader实例< loadbalance >

代理对象是实时生成的,包括class文件,当然只生成接口中有@Adaptive注解的方法

怎么看dubbo的源码_加载器_06

怎么看dubbo的源码_dubbo源码_07


a、dubbo启动加载实现类时,以 key-实例 方式map缓存各个实现类

b、实际调用时,通过key --取实现需要那个实现

c、调用的发生,由生成的代理对象的来发起,最终是从URL总线中,找出extName值,

extName做为key,在缓存map中取出正确的实现实现类