当前位置: 首页 > 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类&…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...