SPI机制源码:JDK Dubbo Spring
JDK 17
Dubbo 3.1.6
JDK SPI
JDK SPI在sql驱动类加载、以及slf4j日志实现加载方面有具体实现。
示例

public class Test {private static final Logger logger = LoggerFactory.getLogger(Test.class);public static void main(String[] args) {ServiceLoader<JdkSpiService> services = ServiceLoader.load(JdkSpiService.class);Set<JdkSpiService> jdkSpiServices = services.stream().map(ServiceLoader.Provider::get).collect(Collectors.toSet());// for (JdkSpiService service : services) {
// logger.info(service.test());
// }}
}
ServiceLoader
由ServiceLoader提供,该类实现了Iterable接口,懒加载SPI实现类,所以加载逻辑都封装在内部迭代器类LazyClassPathLookupIterator implements Iterator里,当迭代器迭代时,会调用#hasNext,此时会调用#hasNextService 该方法又会调用#nextProviderClass加载SPI类:
public final class ServiceLoader<S>implements Iterable<S>
{private final class LazyClassPathLookupIterator<T>implements Iterator<Provider<T>>{static final String PREFIX = "META-INF/services/";// 将相对路径变为绝对路径存储,存储多个SPI接口文件Enumeration<URL> configs;// 每个SPI文件中的实现类全类名Iterator<String> pending;/*** Loads and returns the next provider class.*/private Class<?> nextProviderClass() {// 首次获取spi实现类,会先从META-INF/services/接口全类名中读取所有的记录到configs中if (configs == null) {try {// 相对路径名 Reference PathString fullName = PREFIX + service.getName();// ********************************************************// 需要通过类加载器加载资源,下面根据类加载器类型加载fullName资源// ********************************************************if (loader == null) {configs = ClassLoader.getSystemResources(fullName);} else if (loader == ClassLoaders.platformClassLoader()) {...} else {// 一般走这里// 一般为线程上下文类加载器configs = loader.getResources(fullName);}} catch (IOException x) {fail(service, "Error locating configuration files", x);}}// 第一次为null进入,或者已经执行完毕没有了再进入// 迭代同一个接口文件中的内容时,该while不会进入while ((pending == null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return null;}// 解析一个接口文件中所有的实现类全类名// URLConnection uc = URL#openConnection;// InputStream in = uc.getInputStream();// BufferedReader r = new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE))pending = parse(configs.nextElement());}// 下一个实现类名String cn = pending.next();try {// 反射实现类加载return Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service, "Provider " + cn + " not found");return null;}}}}
关于sql的SPI中使用了
AccessController#doPrivileged,可以参考AccessController usage。简单来说,就是在SecurityManger中指定了某个jar包的security policy,当该jar包中的方法a调用如System.getProperty()方法时,如果没有包裹doPrivileged,会检查方法a调用栈中所有方法是否都被授予权限。而包裹了doPrivileged,只会检查当前方法a的权限。
不过该AccessController和SecurityManager在 Java 17后即将被移除,且无替代。参见 Class AccessController,关于为什么移除,参见 JEP 411: Deprecate the Security Manager for Removal,简单来说就是SecurityManager解决的两个问题已经不是问题,但是维护它却很费力且性能低。
Dubbo SPI
示例
Dubbo SPI 导入格式:

public class Test {public static void main(String[] args) {// 这里的几个方法都会检查传入的是否是接口以及是否标注@SPIExtensionLoader<DubboSpiTest> extensionLoader = ApplicationModel.defaultModel().getExtensionDirector().getExtensionLoader(DubboSpiTest.class);DubboSpiTest test1 = extensionLoader.getExtension("test1");DubboSpiTest test2 = extensionLoader.getExtension("test2");}
}
JDK SPI对应了一个ServiceLoader类,同样Dubbo SPI对应了一个ExtensionLoader。
LoadingStategy
先看该接口。Dubbo SPI 从META-INF下哪个文件夹加载,加载优先级如何,key同名是否支持覆盖等策略信息均由LoadingStrategy接口提供。
LoadingStrategy代表spi加载策略,该接口提供基本的加载信息,默认3个实现类:
DubboInternalLoadingStrategy: 内部加载策略,key同名不可覆盖DubboLoadingStrategy:key同名可覆盖ServicesLoadingStrategy:key同名可覆盖
3个实现类的初始化方式是通过JDK SPI机制引入,参考ExtensionLoader类:
public class ExtensionLoader<T> {...private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();...// Spliterator接口private static LoadingStrategy[] loadLoadingStrategies() {return stream(load(LoadingStrategy.class).spliterator(), false).sorted().toArray(LoadingStrategy[]::new);} ...// 获取LoadingStrategypublic static List<LoadingStrategy> getLoadingStrategies() {return asList(strategies);}
}
ExtensionLoader
新版本中,ExtensionLoader#getExtensionLoader同ExtensionFactory接口都已被废弃,改进如下:
ExtensionLoader#getExtensionLoader→\rightarrow→ExtensionDirector#getExtensionLoaderExtensionFactory→\rightarrow→ExtensionInjector,其实现类由AdaptiveExtensionFactory→\rightarrow→AdaptiveExtensionInjector,Adaptive是一个门面,具体干活的由SpiExtensionFactory→\rightarrow→SpiExtensionInjector,但是所有干活的逻辑弯弯绕绕后又回到ExtensionLoader中,很奇怪。
最终由 ExtensionLoader#loadExtensionClasses执行代码:
public class ExtensionLoader<T> {...private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();...private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {checkDestroyed();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;}private void loadDirectory(Map<String, Class<?>> extensionClasses, LoadingStrategy strategy, String type) throws InterruptedException {loadDirectoryInternal(extensionClasses, strategy, type);...}private void loadDirectoryInternal(Map<String, Class<?>> extensionClasses, LoadingStrategy loadingStrategy, String type) throws InterruptedException {String fileName = loadingStrategy.directory() + type;// 可用的classLoader集合List<ClassLoader> classLoadersToLoad = new LinkedList<>();// 先加入ExtensionLoader的类加载器if (loadingStrategy.preferExtensionClassLoader()) {ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();// 不能是系统类加载器if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {classLoadersToLoad.add(extensionLoaderClassLoader);}}// 获取classloaderSet<ClassLoader> classLoaders = scopeModel.getClassLoaders();// 如果classloader为空,则用系统类加载器加载SPI文件if (CollectionUtils.isEmpty(classLoaders)) {Enumeration<java.net.URL> resources = ClassLoader.getSystemResources(fileName);}// 否则加入classLoader集合else {classLoadersToLoad.addAll(classLoaders);}// 工具类 多个classLoadersToLoad并行搜寻各个classLoader来加载资源// 但是基本只有一个classLoader,不知道为啥这么设计Map<ClassLoader, Set<java.net.URL>> resources = ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad);// 遍历所有,反射生成SPI实现类并加入到extensionClasses集合中resources.forEach(((classLoader, urls) -> {loadFromClass(extensionClasses,...);}));}}
Spring SPI
注意,该SPI机制属于 org.springframework.core包下,通常用在自定义spring-boot-starter中。
示例

public class TestSpringSpiApplication {public static void main(String[] args) {List<TestSpringSpi> spiList = SpringFactoriesLoader.loadFactories(TestSpringSpi.class, Thread.currentThread().getContextClassLoader());for (TestSpringSpi spi : spiList) {spi.test();}}
}
SpringFactoriesLoader
public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";// 缓存static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();...public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {Assert.notNull(factoryType, "'factoryType' must not be null");ClassLoader classLoaderToUse = classLoader;//classloader未指定就用该类的classloaderif (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// 加载spi实现类的全类名List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);List<T> result = new ArrayList<>(factoryImplementationNames.size());for (String factoryImplementationName : factoryImplementationNames) {result.add(// 反射类加载instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));}AnnotationAwareOrderComparator.sort(result);return result;}// 加载名称,这里主要做前置检查和结果处理public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {... // 进一步检查// 加载所有的spring.factories中内容,并获取,没有就返回空列表return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {// 先从缓存中获取所有加载的springfactories中内容Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}// 初始化加载result = new HashMap<>();try {// 获取到MEAT-INF/spring.factories文件的URL,通常就一个Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);// 加载所有的key=valueProperties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {...}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));// 加入缓存cache.put(classLoader, result);}catch (IOException ex) {...}return result;}
}
总结
不管是哪一类,基本结构步骤就是
- 获取类加载器
- 利用类加载器在固定路径加载SPI文件,
ClassLoader#getResources生成Enumeration<java.net.URL>对象(通常只有一个URL) - 对URL对象实施解析,获取实现类全类名
- 反射实施类加载
Class#forName→\rightarrow→Constructor#newInstance
相关文章:
SPI机制源码:JDK Dubbo Spring
JDK 17 Dubbo 3.1.6 JDK SPI JDK SPI在sql驱动类加载、以及slf4j日志实现加载方面有具体实现。 示例 public class Test {private static final Logger logger LoggerFactory.getLogger(Test.class);public static void main(String[] args) {ServiceLoader<JdkSpiServi…...
Spring Security+jwt+redis+自定义认证逻辑 权限控制
Spring Securityjwtredis自定义认证逻辑 权限控制 1.拦截访问基本思路 2.创建数据库表:角色表(应该6个表,这里只用用户表代替角色表)、权限表、路径表、角色-权限表、权限-路径表 /* SQLyog Professional v12.14 (64 bit) MySQL…...
打游戏什么蓝牙耳机好用?打游戏比较好的蓝牙耳机
游戏耳机提供身临其境的细致声音,同时也是与朋友在线聊天的绝佳通信设备,尤其对于游戏玩家来说,聆听和被聆听的最佳方式之一就是游戏耳机,那2023年到底有哪些值得购买的游戏耳机呢?现在就让我们一起来看看吧。 第一款…...
炔基点击交联试剂1704097-05-1,Alkyne-A-DSBSO crosslinker,发生相应点击反应
1、理论分析:中文名:炔基-A-DSBSO crosslinker,英文名:Alkyne-A-DSBSO crosslinkerCAS号:1704097-05-1化学式:C25H32N2O12S2分子量:616.652、产品详情:外观:白色固体&…...
刷题记录:牛客NC24309Overplanting (Silver)
传送门:牛客 题目描述: Farmer John has purchased a new machine that is capable of planting grass within any rectangular region of his farm that is "axially aligned" (i.e., with vertical and horizontal sides). Unfortunately, the machine malfunc…...
Spring Boot中使用Sa-Token实现轻量级登录与鉴权
1. Sa-Token 介绍 Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 功能结构图 2. 登录认证 对于一些登录之后才能访问的接口(例如&…...
《分布式技术原理与算法解析》学习笔记Day20
CAP理论 什么是CAP理论? CAP理论用来指导分布式系统设计,以保证系统的可用性、数据一致性等。 C,Consistency,一致性,指所有节点在同一时刻的数据是相同的,即更新操作执行结束并响应用户完成后ÿ…...
【2023-2-23】FastDeploy 安装教程
【2023-2-22】FastDeploy 安装编译教程 该测试 FastDeploy CPU版本。 1. fastDeploy库编译 1.1 官方预编译库下载 预编译库下载安装 1.2 自定义CPU版本库编译 官方编译FastDeploy教程 CMakeGUI VS 2019 IDE编译FastDeploy 本人编译教程 CMAKE_CONFIGURATION_TYPES 属性设…...
rollup.js 一个简单实用的打包工具
最近在看vue3相关的知识的时候,发现了一个新的打包工具,至少于我而言是新鲜的。它就是rollup.js。一说到JS打包、合并、压缩、模块处理等都会想到webpack,这是王者,当然入门的难度偏高。而vue3中搭配的vite运行速度确实非常快&…...
数据结构与算法之最小爬楼梯费用动态规划
继续上一道题目,在上一道题目的基础之上,我们来解决这一道爬楼梯最小费用题。一.题目描述二.思路(动态规划五部曲)确定dp数组以及下标的含义使用动态规划,就要有一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了。dp[i]的…...
阿里云ACA认证如何获取?
获取阿里云ACA(Alibaba Cloud Certification Associate)认证,需要按照以下步骤进行操作: 注册阿里云账号。如果您还没有阿里云账号,请先注册一个账号。登录阿里云官网。登录后,进入阿里云认证中心。选择AC…...
【Python入门第十六天】Python If ... Else
Python 条件和 If 语句 Python 支持来自数学的常用逻辑条件: 等于:a b不等于:a ! b小于:a < b小于等于:a < b大于:a > b大于等于:a > b 这些条件能够以多种方式使用,…...
两数之和的解法
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案…...
领导催我优化SQL语句,我求助了ChatGPT。这是ChatGPT给出的建议,你们觉得靠谱吗
作为一个程序员,无论在面试还是工作中,优化SQL都是绕不过去的难题。 为啥?工作之后才会明白,随着公司的业务量增多,SQL的执行效率对程系统运行效率的影响逐渐增大,相对于改造代码,优化SQL语句是…...
ArcGIS手动分割矢量面要素从而划分为多个面部分的方式:Cut Polygons Tool
本文介绍在ArcGIS下属ArcMap软件中,通过“Cut Polygons Tool”工具,对一个面要素矢量图层加以手动分割,从而将其划分为指定形状的多个部分的方法。 对于一个面要素矢量文件,有时我们需要对其加以划分,通过手动勾勒新的…...
【LeetCode】剑指 Offer 13. 机器人的运动范围 p92 -- Java Version
题目链接:https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/ 1. 题目介绍(13. 机器人的运动范围) 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动࿰…...
[oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星
编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物而是 生活中常见的 家用电器 ibm 蓝色巨人脚步沉重 dec 小型机不断蚕食低端市场甚至组成网络干掉大型机…...
crontab 执行脚本报错,手动执行脚本正常的解决方法
一、出现的问题 有一个守护脚本XXX.sh,需要使用oracle用户在linux上配置定时任务,每1分钟检查执行一次。但是发现该脚本使用oralce用户手动启动没问题,能正常把程序启动起来,而使用crontab并没有把程序启动起来。 二、排查分析问…...
扎心话题 | 设计院背后的潜规则你知道吗?
大家好,我是建模助手。 大家都知道,在过去的2022年经济是真难!以小编所在的广东为例,全年GDP增长仅1.9%。 这个数据足以呈现一个社会现象——不仅消费力咔咔下降,各行各业更有不同程度地嗝屁。这其中也包括一些设计院…...
【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
