dubbo spi

1.简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组

件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

2.Java SPI 示例

通过一个示例演示 Java SPI 的使用方法。

public interface IRobot {

    String sayHello();
}

public class BigBee implements IRobot {

    @Override
    public String sayHello() {
        return "BigBee";
    }
}

public class OptimusPrime implements IRobot {

    @Override
    public String sayHello() {
        return "OptimusPrime";
    }
}

在resources下创建一个META-INF/services/com.katcat.myserviceloader.provider.service.IRobot
com.katcat.myserviceloader.bigbee.impl.OptimusPrime
com.katcat.myserviceloader.bigbee.impl.BigBee

public class MainApp {

    public static void main(String[] args) {
        ServiceLoader<IRobot> printerLoader = ServiceLoader.load(IRobot.class);
        for (IRobot robot : printerLoader) {
            System.out.println(robot.sayHello());
        }
    }
}

重点是ServiceLoader和LazyIterator。

ServiceLoader重写了iterator方法,实际是lookupIterator.next();

LazyIterator重要的四个方法,1.hasNextService 2.nextService 3.hasNext 4.next,前两个是private,为后面两个public服务的。

看源码。

问题:

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

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

  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

3.DUBBO SPI 源码分析

准备工作:

1.ExtensionLoader 扩展点加载器。

2.ExtensionFactory 扩展类实现类实例工厂。

3.@SPI 扩展点接口的标识。

4.@Adaptive 标注在类上或者方法上,表示需要被扩展。

5.@Activate 可以被框架中自动激活的加载扩展

3.1 Protocol

<pre style="margin: 10px 0px 0px; padding: 0px; font-family: ConfluenceInstalledFont, monospace; color: rgb(23, 43, 77); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">以Protocol为例,有现成的好几个扩展,且后续服务暴露和引用都和其有关。</pre>

@SPI("dubbo")->看Protocol代码
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

adaptive设计的目的是为了识别固定已知类和扩展未知类。
1.注解在类上:代表人工实现,实现一个装饰类(设计模式中的装饰模式),它主要作用于固定已知类,
目前整个系统只有2个,AdaptiveCompiler、AdaptiveExtensionFactory。
a.为什么AdaptiveCompiler这个类是固定已知的?因为整个框架仅支持Javassist和JdkCompiler。
a.为什么AdaptiveExtensionFactory这个类是固定已知的?因为整个框架仅支持2个objFactory,一个是spi,另一个是spring
2.注解在方法上:代表自动生成和编译一个动态的Adpative类,它主要是用于SPI,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类.

//以这行代码来讲解
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("thrift");

3.2 核心的类 ExtensionLoader

重点关注以下三个方法

  • getActivateExtension :根据条件获取当前扩展可自动激活的实现
  • getExtension : 根据名称获取当前扩展的指定实现
  • getAdaptiveExtension : 获取当前扩展的自适应实现

/**
 * 私有的构造方法
 * 提供getExtensionLoader(java.lang.Class)出去
 * 节省系统资源
 * @param type
 */
private ExtensionLoader(Class<?> type) {
    this.type = type;
    //ExtensionFactory主要用于加载扩展的实现
    //通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

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!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    //从缓存获取一个ExtensionLoader,
    // 第一步会先创建一个ExtensionFactory的ExtensionLoader,
    // 第二步会创建一个Compire的ExtensionLoader
    //最后成功创建Protocol的ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //保证对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

ExtensionFactory看这个类的源码

<pre style="margin: 10px 0px 0px; padding: 0px; font-family: ConfluenceInstalledFont, monospace; color: rgb(23, 43, 77); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">AdaptiveExtensionFactory -> 源码</pre>

getAdaptiveExtension调用流程

看源码

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

getExtension调用流程

看源码

  1. getExtension(name)
  2. -> createExtension(name) #如果无缓存则创建
  3. -> getExtensionClasses().get(name) #获取name对应的扩展类型
  4. -> 实例化扩展类
  5. -> injectExtension(instance) # 扩展点注入
  6. -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入
//DI IOC
private T injectExtension(T instance) {
    try {
        //ExtensionFactory的objectFactory是null
        if (objectFactory != null) {
            //拿到所有的方法
            for (Method method : instance.getClass().getMethods()) {
                //判断是否是set开头的方法
                if (isSetter(method)) {
                    /**
                     * Check {@link DisableInject} to see if we need auto injection for this property
                     */
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    Class<?> pt = method.getParameterTypes()[0];
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        //拿到需要set的属性名
                        String property = getSetterProperty(method);
                        //获取该属性名对应的扩展
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

  • 每个ExtensionLoader实例只负责加载一个特定扩展点实现
  • 每个扩展点对应最多只有一个ExtensionLoader实例
  • 对于每个扩展点实现,最多只会有一个实例
  • 一个扩展点实现可以对应多个名称(逗号分隔)
  • 对于需要等到运行时才能决定使用哪一个具体实现的扩展点,应获取其自使用扩展点实现(AdaptiveExtension)
  • @Adaptive注解要么注释在扩展点@SPI的方法上,要么注释在其实现类的类定义上
  • 如果@Adaptive注解注释在@SPI接口的方法上,那么原则上该接口所有方法都应该加@Adaptive注解(自动生成的实现中默认为注解的方法抛异常)
  • 每个扩展点最多只能有一个被AdaptiveExtension
  • 每个扩展点可以有多个可自动激活的扩展点实现(使用@Activate注解)

4.总结

java spi和dubbo spi的区别

1.Dubbo 支持更多的加载路径

2.java需要加载所有的实现类,dubbo不是通过Iterator的形式,而是直接通过名称来定位具体的Provider,按需要加载,效率更高。

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

4.线程安全

附录:

o spi

FROM : https://www.cnblogs.com/heart-king/p/5632524.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容