参考文章:Dubbo的SPI机制分析

首先来看看 Java SPI 的机制

Java SPI 起初是提供给厂商做插件开发用的,例如数据库驱动java.sql.Driver,市面上各种各样的数据库,不同的数据库底层协议都不一样,为了方便开发者调用数据库而不用关心它们之间的差异,因此必须提供一个统一的接口来规范和约束这些数据库。有了统一的接口,数据库厂商就可以按照规范去开发自己的数据库驱动了。

厂商开发好数据库驱动了,应用如何使用呢?该使用哪个驱动呢?以 MySQL 为例,早期手写 JDBC 时,开发者需要手动注册驱动,现在已经不需要了,就是利用了 SPI 机制。

Java SPI 使用了策略模式,一个接口多种实现,开发者面向接口编程,具体的实现并不在程序中直接硬编码,而是通过外部文件进行配置。

Java SPI 约定了一个规范,使用步骤如下:

  • 编写一个接口。
  • 编写具体实现类。
  • 在 ClassPath 下的META-INF/services,目录创建以接口全限定名命名的文件,文件内容为实现类的全限定名,多个实现用换行符分割。
  • 通过 ServiceLoader 类获取具体实现。

接口:

实现类:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}

简单实例

package org.sun.spi.services;/*** 这是java 的 spi, 没有@SPI注解, 类似 mySQL*/
public interface Say {void say();
}

实现类1

package org.sun.spi.impl;import org.sun.spi.services.Say;public class SayImpl implements Say {@Overridepublic void say() {System.out.println("nihao .....");}
}

实现类2

package org.sun.spi.impl;import org.sun.spi.services.Say;public class SayWrapper implements Say {@Overridepublic void say() {System.out.println("hello SayWrapper。。。。");}
}

META-INF/services 文件

测试:

public class Main {public static void main(String[] args) {ServiceLoader<Say> serviceLoader = ServiceLoader.load(Say.class);serviceLoader.forEach(say -> say.say());}

结果:

nihao .....
hello SayWrapper。。。。

Dubbo SPI机制

Dubbo SPI 定义了一套自己的规范,同时对 Java SPI 存在的问题进行了改进,优点如下:

  • 扩展类按需加载,节约资源。
  • SPI 文件采用 Key=Value 形式,可以根据扩展名灵活获取实现类。
  • 扩展类对象做了缓存,避免重复创建。
  • 扩展类加载失败有详细日志,方便排查。 支持 AOP 和 IOC。

Dubbo SPI 使用规范:

  • 编写接口,接口必须加@SPI 注解,代表它是一个可扩展的接口。
  • 编写实现类。
  • 在 ClassPath 下的META-INF/dubbo,目录创建以接口全限定名命名的文件,文件内容为 Key=Value 格式,Key 是扩展点的名称,Value 是扩展点实现类的全限定名。
  • 通过 ExtensionLoader 类获取扩展点实现。

Dubbo 默认会扫描META-INF/services
、META-INF/dubbo
、META-INF/dubbo/internal
三个目录下的配置,第一个是为了兼容 Java SPI,第三个是 Dubbo 内部使用的扩展点。

测试用例

// TODO

源码分析:

ExtensionLoader.getExtensionLoader

getExtension()

public T getExtension(String name, boolean wrap) {checkDestroyed();if (StringUtils.isEmpty(name)) {throw new IllegalArgumentException("Extension name == null");}// 如果 name 为true,则返回一个默认的扩展点if ("true".equals(name)) {return getDefaultExtension();}String cacheKey = name;if (!wrap) {cacheKey += "_origin";}// 创建或者返回一个holder对象, 用于缓存扩展类的实例final Holder<Object> holder = getOrCreateHolder(cacheKey);Object instance = holder.get();// 如果缓存不存在则创建一个实例if (instance == null) {// 同步设置,懒汉模式synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtension(name, wrap);holder.set(instance);}}}return (T) instance;

PS : 有意思的类,学习

/*** 持有一个泛型* Helper Class for hold a value.*/
public class Holder<T> {// 保证线程可见性private volatile T value;public void set(T value) {this.value = value;}public T get() {return value;}}

上述代码就是先查缓存,如果未命中,则创建一个扩展对象,createExtension() 应该就是去指定的路径下查找name 对应的扩展点实现,并且实例化之后返回。

@SuppressWarnings("unchecked")private T createExtension(String name, boolean wrap) {// 根据 name 返回扩展类Class<?> clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {throw findException(name);}try {// 从缓存中查找该类是否已经初始化// ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);T instance = (T) extensionInstances.get(clazz);if (instance == null) {// 如果没有,则重新创建一个实例并加入缓存, 反射机制extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));instance = (T) extensionInstances.get(clazz);// 初始化前的相关操作instance = postProcessBeforeInitialization(instance, name);// 依赖注入injectExtension(instance);instance = postProcessAfterInitialization(instance, name);}if (wrap) {// 通过 Wrapper 进行包装List<Class<?>> wrapperClassesList = new ArrayList<>();if (cachedWrapperClasses != null) {wrapperClassesList.addAll(cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if (CollectionUtils.isNotEmpty(wrapperClassesList)) {for (Class<?> wrapperClass : wrapperClassesList) {Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);boolean match = (wrapper == null) ||((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&!ArrayUtils.contains(wrapper.mismatches(), name));if (match) {instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));instance = postProcessAfterInitialization(instance, name);}}}}// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.initExtension(instance);return instance;} catch (Throwable t) {throw new IllegalStateException("Extension instance (name: " + name + ", class: " +type + ") couldn't be instantiated: " + t.getMessage(), t);}}

继续跟踪getExtensionClasses

  • 从缓存中获取已经被加载的扩展类
  • 如果未命中缓存, 则调用loadExtensionClasses 加载扩展类
 private Map<String, Class<?>> getExtensionClasses() {// Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();Map<String, Class<?>> classes = cachedClasses.get();if (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = loadExtensionClasses();cachedClasses.set(classes);}}}return classes;

loadExtensionClasses()

/*** synchronized in getExtensionClasses*/private Map<String, Class<?>> loadExtensionClasses() {checkDestroyed();// 获得当前type扩展接口的默认扩展对象, 并且缓存cacheDefaultExtensionName();Map<String, Class<?>> extensionClasses = new HashMap<>();for (LoadingStrategy strategy : strategies) {// 加载指定文件目录下的配置文件loadDirectory(extensionClasses, strategy, type.getName());// compatible with old ExtensionFactoryif (this.type == ExtensionInjector.class) {loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());}}return extensionClasses;}

(精彩后续,请看下回分解)

Dubbo SPI机制学习总结(持续更新...)相关推荐

  1. 重拾CCNA,学习笔记持续更新ing......(4)

    重拾CCNA,学习笔记持续更新ing......(4) 路由器作用功能的经典解说(笑)(非原创) 假设你的名字叫小不点,你住在一个大院子里,你的邻居有很多小伙伴,在门口传达室还有个看大门的李大爷,李大 ...

  2. Dubbo SPI机制(上):一个普通的扩展类是如何加载的

    这一篇我们先不讲Dubbo中的具体业务逻辑,我们来打基础,聊一聊Dubbo中的SPI机制. Dubbo SPI是干啥的 了解一个技术,得先知道它是为了解决什么问题而产生的.那么Dubbo SPI是干什 ...

  3. iOS开发- ios学习资源(持续更新)

    mark一些自己在学习过程中收集的资源.免得需要的时候没地方找. 持续更新.(最新更新时间: 2014.4.4) 1.苹果官方文档 构建iOS程序:下面的这篇文章介绍了 iOS 程序开发的过程: St ...

  4. Dubbo源码分析系列-深入Dubbo SPI机制

    导语   在之前的博客中介绍过关于Java中SPI的机制,也简单的分析了关于Java中SPI怎么去使用.SPI的全称Service Provider Interface,是一种服务发现机制.SPI的本 ...

  5. Python进阶和高阶学习(持续更新)

    Python是一门非常方便的静态语言,使用语法简洁,语言格式更易于让大众理解,在当今的大数据的浪潮下,Python的数据分析,机器学习等等起到了巨大的作用,因此学习Python必不可少. 当然在我看来 ...

  6. Admin.NET管理系统(vue3等前后端分离)学习笔记--持续更新

    我的学习笔记 - 9iAdmin.NET 欢迎学习交流 (一)前端笔记 1.1 关于.env的设置 1.2 关于路由模式问题 1.3 关于 vue.config.ts 1.4 关于 打包(pnpm r ...

  7. 【dubbo源码解析】 --- dubbo spi 机制(@SPI、@Adaptive)详解

    本文对应源码地址:https://github.com/nieandsun/dubbo-study 注意:dubbo 要求SPI扩展点的实现类必须要有一个无参构造,除了Wrapper实现类之外 文章目 ...

  8. Dubbo SPI机制和原理解析

    简介 SPI(service provider interface)是一种服务发现机制,通过加载指定路径下配置文件中的实现类,达到运行时用实现动态替换接口的目的.SPI常常用于扩展应用的功能,Dubb ...

  9. Oracle项目管理之Primavera Unifier学习地图(持续更新)

    目录 卷首语-学习地图 OracleUnifier基本功能介绍 OracleUnifier项目实施要点 OracleUnifier 常规业务管理 OracleUnifier用户管理 OracleUni ...

最新文章

  1. 6 不更新无法使用_win10更新后无线网络无法使用
  2. [zz]mysql 和 mongo db 语法对比
  3. html转excel有问题,html转excel
  4. 计算矩阵的逆和行列式的值(高斯消元+LU分解)
  5. leetcode —— 31. 下一个排列
  6. Mac os更新系统后安装scrapy报错error: command ‘xcrun‘ failed with exit status 1
  7. jQuery EasyUI -ComboBox(下拉列表框)使用
  8. 论文笔记《Attention Is All You Need》
  9. 独立游戏开发(十七)-- 发布Taptap
  10. 关于 Android 的 OMA DRM 验证
  11. 龙少的Linux配置大全
  12. win10+Ubuntu双系统安装/卸载/扩容/同步时间
  13. 制作一个每日一图小工具
  14. linux 读取命令行输入参数,shell脚本读取文件+读取命令行参数+读取标准输入+变量赋值+输出到文件...
  15. MVG学习笔记(8) --背景和平面几何
  16. [每天读一点英文:那些给我勇气的句子]:the paradox of happiness
  17. 05 Bean实例化整体流程
  18. Java超市收银系统(连接数据库实现具体功能)(源码——即搬可用)
  19. 腾讯领投,小鱼易连完成C轮融资
  20. 人工智能的四波浪潮以及未来的发展方向

热门文章

  1. 3分钟学会Excel“自主学习”
  2. 在一个地方上班上久了,会出现想辞职的想法吗?
  3. 智能写作,产品总监竟然........
  4. 极度瘦身:打造200MB的XP安装盘(转)
  5. OpenGL3.3视锥体
  6. Python爬虫将爬取的图片写入world文档
  7. 自媒体清扫行动开始,多家头部区块链媒体被关
  8. wireshark流量分析实战
  9. JS实现浏览器打印Word
  10. 安装densepose (make、make ops问题解决)