系统整理下Java SPI,Dubbo SPI,Spring SPI。

SPI:Serial Peripheral Interface 串行外设接口

Java SPI

简述

在文件中写实现类的全路径名。调用ServiceLoader.load的时候返回一个迭代器,他内部是一个懒加载,当调用hasNext的时候才会根据全路径名读取文件,调用next的时候才会实例化。

本质上就是,获取接口全路径名,安规范去该路径下按行读取文件,然后用同一个类加载器加载类,返回。(源码很简单,就不多说了,使用方法看图)

Java spc分析实例_加载

优点

类似于模板方法,定义好接口,实现可以完全由第三方来写,按照规范就行。

缺点

人家自己也说了,一个简单的服务提供者的加载工具。人家源码开发者就没想写的多好用,就是写个demo秀操作呢。

Java spc分析实例_加载_02

Dubbo SPI

简介

官网:https://dubbo.apache.org/zh/docs/v2.7/dev/spi/

优点

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

分析

一下内容在META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionInjector文件中,此处可以看到是key,value的形式

Java spc分析实例_后端_03


具体也是加载进内存,先缓存起来

Java spc分析实例_后端_04

Java spc分析实例_ruby_05

当第一次调用getExtesion的时候才创建

Java spc分析实例_后端_06

Java spc分析实例_java_07

这样就可以通过key就可以拿到具体的实例了

Java spc分析实例_ruby_08

有一类比较特殊就是active,这个可以简单的理解为轻松的获取到对应的模版,如果文件中没有指定,就字节码创建

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

Java spc分析实例_java_09


这个是其中一个例子,生成字节码然后编译

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {


public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}


public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
		if (arg1 == null) throw new IllegalArgumentException("url == null");
		com.alibaba.dubbo.common.URL url = arg1;
		String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
		if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
		com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
		return extension.refer(arg0, arg1);
}


public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
}

Spring SPI

spring spi更像是多个文件整合到了一个文件中

Java spc分析实例_后端_10

Java spc分析实例_Java spc分析实例_11