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

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...