当前位置: 首页 > news >正文

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的权限。
不过该AccessControllerSecurityManager在 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#getExtensionLoaderExtensionFactory接口都已被废弃,改进如下:

  • ExtensionLoader#getExtensionLoader →\rightarrow ExtensionDirector#getExtensionLoader
  • ExtensionFactory →\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;}
}

总结

不管是哪一类,基本结构步骤就是

  1. 获取类加载器
  2. 利用类加载器在固定路径加载SPI文件,ClassLoader#getResources生成Enumeration<java.net.URL>对象(通常只有一个URL
  3. 对URL对象实施解析,获取实现类全类名
  4. 反射实施类加载 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.创建数据库表&#xff1a;角色表&#xff08;应该6个表&#xff0c;这里只用用户表代替角色表&#xff09;、权限表、路径表、角色-权限表、权限-路径表 /* SQLyog Professional v12.14 (64 bit) MySQL…...

打游戏什么蓝牙耳机好用?打游戏比较好的蓝牙耳机

游戏耳机提供身临其境的细致声音&#xff0c;同时也是与朋友在线聊天的绝佳通信设备&#xff0c;尤其对于游戏玩家来说&#xff0c;聆听和被聆听的最佳方式之一就是游戏耳机&#xff0c;那2023年到底有哪些值得购买的游戏耳机呢&#xff1f;现在就让我们一起来看看吧。 第一款…...

炔基点击交联试剂1704097-05-1,Alkyne-A-DSBSO crosslinker,发生相应点击反应

1、理论分析&#xff1a;中文名&#xff1a;炔基-A-DSBSO crosslinker&#xff0c;英文名&#xff1a;Alkyne-A-DSBSO crosslinkerCAS号&#xff1a;1704097-05-1化学式&#xff1a;C25H32N2O12S2分子量&#xff1a;616.652、产品详情&#xff1a;外观&#xff1a;白色固体&…...

刷题记录:牛客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 权限认证框架&#xff0c;主要解决&#xff1a;登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 功能结构图 2. 登录认证 对于一些登录之后才能访问的接口&#xff08;例如&…...

《分布式技术原理与算法解析》学习笔记Day20

CAP理论 什么是CAP理论&#xff1f; CAP理论用来指导分布式系统设计&#xff0c;以保证系统的可用性、数据一致性等。 C&#xff0c;Consistency&#xff0c;一致性&#xff0c;指所有节点在同一时刻的数据是相同的&#xff0c;即更新操作执行结束并响应用户完成后&#xff…...

【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相关的知识的时候&#xff0c;发现了一个新的打包工具&#xff0c;至少于我而言是新鲜的。它就是rollup.js。一说到JS打包、合并、压缩、模块处理等都会想到webpack&#xff0c;这是王者&#xff0c;当然入门的难度偏高。而vue3中搭配的vite运行速度确实非常快&…...

数据结构与算法之最小爬楼梯费用动态规划

继续上一道题目&#xff0c;在上一道题目的基础之上&#xff0c;我们来解决这一道爬楼梯最小费用题。一.题目描述二.思路(动态规划五部曲)确定dp数组以及下标的含义使用动态规划&#xff0c;就要有一个数组来记录状态&#xff0c;本题只需要一个一维数组dp[i]就可以了。dp[i]的…...

阿里云ACA认证如何获取?

获取阿里云ACA&#xff08;Alibaba Cloud Certification Associate&#xff09;认证&#xff0c;需要按照以下步骤进行操作&#xff1a; 注册阿里云账号。如果您还没有阿里云账号&#xff0c;请先注册一个账号。登录阿里云官网。登录后&#xff0c;进入阿里云认证中心。选择AC…...

【Python入门第十六天】Python If ... Else

Python 条件和 If 语句 Python 支持来自数学的常用逻辑条件&#xff1a; 等于&#xff1a;a b不等于&#xff1a;a ! b小于&#xff1a;a < b小于等于&#xff1a;a < b大于&#xff1a;a > b大于等于&#xff1a;a > b 这些条件能够以多种方式使用&#xff0c…...

两数之和的解法

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案…...

领导催我优化SQL语句,我求助了ChatGPT。这是ChatGPT给出的建议,你们觉得靠谱吗

作为一个程序员&#xff0c;无论在面试还是工作中&#xff0c;优化SQL都是绕不过去的难题。 为啥&#xff1f;工作之后才会明白&#xff0c;随着公司的业务量增多&#xff0c;SQL的执行效率对程系统运行效率的影响逐渐增大&#xff0c;相对于改造代码&#xff0c;优化SQL语句是…...

ArcGIS手动分割矢量面要素从而划分为多个面部分的方式:Cut Polygons Tool

本文介绍在ArcGIS下属ArcMap软件中&#xff0c;通过“Cut Polygons Tool”工具&#xff0c;对一个面要素矢量图层加以手动分割&#xff0c;从而将其划分为指定形状的多个部分的方法。 对于一个面要素矢量文件&#xff0c;有时我们需要对其加以划分&#xff0c;通过手动勾勒新的…...

【LeetCode】剑指 Offer 13. 机器人的运动范围 p92 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/ 1. 题目介绍&#xff08;13. 机器人的运动范围&#xff09; 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动&#xff0…...

[oeasy]python0091_仙童公司_八叛逆_intel_8080_altair8800_牛郎星

编码进化 个人电脑 计算机 通过电话网络 进行连接 极客 利用技术 做一些有趣的尝试 极客文化 是 认真研究技术的 文化 计算机 不再是 高校和研究机构高墙里面的 神秘事物而是 生活中常见的 家用电器 ibm 蓝色巨人脚步沉重 dec 小型机不断蚕食低端市场甚至组成网络干掉大型机…...

crontab 执行脚本报错,手动执行脚本正常的解决方法

一、出现的问题 有一个守护脚本XXX.sh&#xff0c;需要使用oracle用户在linux上配置定时任务&#xff0c;每1分钟检查执行一次。但是发现该脚本使用oralce用户手动启动没问题&#xff0c;能正常把程序启动起来&#xff0c;而使用crontab并没有把程序启动起来。 二、排查分析问…...

扎心话题 | 设计院背后的潜规则你知道吗?

大家好&#xff0c;我是建模助手。 大家都知道&#xff0c;在过去的2022年经济是真难&#xff01;以小编所在的广东为例&#xff0c;全年GDP增长仅1.9%。 这个数据足以呈现一个社会现象——不仅消费力咔咔下降&#xff0c;各行各业更有不同程度地嗝屁。这其中也包括一些设计院…...

【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…...

python namedtuple

## Python 中的 Any&#xff1a;一个被低估的类型注解工具 在 Python 的类型注解体系里&#xff0c;Any 是一个看似简单&#xff0c;却常常引发误解的特殊类型。很多开发者第一次见到它时&#xff0c;可能会觉得这不过是个“万金油”式的占位符&#xff0c;用来应付那些暂时不想…...

15 指挥AI写算法代码:排序、递归、数据结构快速生成

指挥AI写算法代码:排序、递归、数据结构快速生成 摘要 本文为《30天掌控AI编程:从指令到落地,手把手教你指挥AI写代码》系列第十五篇,属于第三阶段多场景实战核心内容。本篇聚焦算法与数据结构代码高效生成,打破传统算法学习需手动推导逻辑、死记语法、反复调试的困境,…...

go-zero 数据库自动化:从 SQL 到 CRUD 的生产级实践指南

go-zero 数据库自动化:从 SQL 到 CRUD 的生产级实践指南 一、先说结论:数据库自动化不是“偷懒”,而是工程标准化 在中大型后端系统里,数据库访问层往往有两个典型矛盾: 业务迭代要求快,表结构一变,CRUD、缓存、查询接口都得跟着改。 生产环境要求稳,任何一处 SQL、事…...

测评 ASR 歌词生成模型

1. 测评背景与目标 业务需求&#xff1a; 目前有大批量的 MP3 音频需要匹配歌词。网络公开渠道能爬取到的歌词占比不足 50%&#xff0c;因此必须采用 ASR&#xff08;自动语音识别&#xff09;生成模式来补全缺口。 核心痛点&#xff1a; 现有的商业 API 调用成本较高&#xf…...

OpenClaw语音控制扩展:Gemma-3-12b-it实现自然语言任务触发

OpenClaw语音控制扩展&#xff1a;Gemma-3-12b-it实现自然语言任务触发 1. 为什么需要语音控制自动化助手 上周五下班路上&#xff0c;我遇到一个典型场景&#xff1a;开车时收到客户紧急邮件需要立即回复&#xff0c;但双手离不开方向盘。这种场景让我开始思考——能否用语音…...

批量新员工入职培训怎么做?行政/销售/技术等5大核心岗位培训重点拆解

年后复工、校招季、业务扩招&#xff0c;一次入职几十上百人&#xff0c;覆盖销售、客服、运维、行政、技术、生产等多个岗位。这是企业培训中非常普遍、甚至是常态的管理场景&#xff0c;尤其在中大型企业、连锁企业、制造型企业、互联网/科技公司里&#xff0c;同时管理多岗位…...

2025届学术党必备的降重复率神器实际效果

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 于人工智能生成内容即AIGC广泛运用的背景状况之下&#xff0c;将AIGC率予以降低成了内容创作…...

EcomGPT-中英文-7B电商模型在Vue.js前端项目中的集成:打造实时智能客服聊天组件

EcomGPT-中英文-7B电商模型在Vue.js前端项目中的集成&#xff1a;打造实时智能客服聊天组件 最近在做一个电商后台的升级项目&#xff0c;客户提了个需求&#xff0c;希望能在用户端和管理后台都加上一个智能客服&#xff0c;能实时回答商品咨询、订单状态这些常见问题。一开始…...

UBANTU安装Duckietown细节操作与错误记录

一&#xff0c;安装 1.虚拟机安装VM&#xff0c;安装UBUNTU系统&#xff0c;按照VMware虚拟机安装Ubuntu教程(超详细)_vmware安装ubuntu-CSDN博客 去操作就可以&#xff0c;绝对详细&#xff0c;而且不坑。 2.个人建议使用搜狗输入法。 3.打开系统文件夹 例如我的叫tuoni&a…...

3分钟彻底移除Windows Edge浏览器:系统优化终极指南

3分钟彻底移除Windows Edge浏览器&#xff1a;系统优化终极指南 【免费下载链接】EdgeRemover A PowerShell script that correctly uninstalls or reinstalls Microsoft Edge on Windows 10 & 11. 项目地址: https://gitcode.com/gh_mirrors/ed/EdgeRemover 你是否…...