问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

从JDBC Driver 看 Java 的 SPI 机制与应用

发布网友 发布时间:2024-09-29 08:02

我来回答

1个回答

热心网友 时间:2024-10-01 19:38

JDBC API 是 JDK 标准的一部分,定义了一组 Java 与数据库交互的接口。数据库种类繁多,具体如何交互的实现由数据库厂商提供。以 MySQL 为例,在 JDBC API 4.0 版本之后,我们只需引入提供的 drivermysql-connector-java 作为依赖,然后就可以调用 JDBC API 创建连接并执行 SQL 与数据库交互了。然而,我们的程序是如何定位到 driver 具体的位置并正确加载的呢?这就涉及到 SPI 机制了。

SPI(Service Provider Interface)可以说是一种面向扩展的设计模式。应用的核心逻辑通过面向接口的编程思路,并设计可扩展点。在运行时,通过服务发现机制加载扩展点的具体实现,从而达到在不修改核心逻辑的情况下,具体实现可灵活替换的效果。

Java 6 提供了 SPI 机制,它包含了两类角色:服务定义者和服务实现者。服务定义者涉及到的概念有:

服务实现者涉及到的概念有:

接下来,我们通过一个简单的案例来聊聊 SPI 机制的原理。例如,我们的服务需要对消费者提供支付能力,但实际的支付能力可能由支付宝或者微信提供。作为服务定义者,我们首先定义服务的标准接口,它本身就是一个普通的 Java Interface,比如我们定义我们的 SPI:

这个接口应该被公开出来供服务提供者实现,我们可以把它放到一个独立的包中去发布,比如叫做 my-system-spi。接下来就是服务提供者去实现了。首先引入包含 SPI 定义的包:

然后进行对它的实现:

同时,服务提供者需要对外告知自己有对某个 SPI 的实现。告知的方式有一个约定,就是在 Jar 包的 META-INF/services 文件夹中,定义一个以 SPI 全限定名为文件名的文件,文件内定义 SPI 实现类的全限定名,比如我们需要创建一个 META-INF/services/me.leozdgao.demo.spi.Payment 文件,文件的内容如下:

如果一个 Jar 包中有多个实现,则可以都列出来并通过换行符分割即可。

服务提供者完成实现后,发布自己的 Jar 包。那么接下来我们就需要去加载它了,这就涉及到最关键的 ServiceLoader。我们先看看如何使用:

可以看到我们通过调用 ServiceLoader.load 方法并传入我们的 SPI 接口来创建了一个 ServiceLoader 实例。由于 ServiceLoader 实现了 Iterator 迭代器接口,通过访问迭代器就可以获取实现了 SPI 的服务。如果有多个 SPI 的实现的话,具体采用哪个就需要自行处理判断了。

这样我们就在运行时顺利完成了服务的加载。未来我们如果要对 SPI 的实现要做替换,也完全不需要修改我们的逻辑代码。

ServiceLoader 的服务加载实现原理是,ServiceLoader.load 方法本质就是创建一个 ServiceLoader 实例,而服务加载主要在 ServiceLoader 的 Lazy Iterator 实现中。我们来看看迭代器方法的实现逻辑:

具体源码请参考 JDK 源码:java.util.ServiceLoader

由于是 Lazy Iterator,ServiceLoader 实例并不会一开始就去找到所有的实现,而是在不断的调用迭代器的过程中去懒加载实现类、完成实例化,并将实现类实例化的结果缓存起来。服务的初始化逻辑也反映出了两个约定:

MySQL Driver 如何实现 SPI:上面介绍完 SPI 的实现机制后,再来回答开头的问题:我们的程序是如何定位到 driver 具体的位置并正确加载的?这个就是 DriverManager 的实现了。

在 DriverManager 的静态方法 getDrivers、getDriver、drivers、getConnection 中都会调用一个 ensureDriversInitialized 方法,这个方法会保证 driver 的初始化并仅执行一次,我们具体看一下它的实现逻辑:

具体源码请参看 JDK 源码:java.sql.DriverManager

通过两种方式进行 driver 的初始化,一种是通过系统参数 jdbc.drivers 指定,通过反射的方式调用 Class.forName 进行初始化,另一种就是利用 ServiceLoader.load(Driver.class) 找到服务提供方,通过调用迭代器触发服务的初始化。

服务具体的初始化方式利用的是类的静态代码块机制,以 MySQL 的 driver 为例:

可以看到 Driver 的实现包含一个静态代码块,在通过反射 Class.forName 或者被 ServiceLoader 迭代器初始化时,都可以触发它的执行,在这里调用了 DriverManager.registerDriver 进行了注册。

那么如果发现了多个 driver 的话,要选择哪一个具体的实现呢?那就是 JDBC URL 了。driver 的实现有一个约定,如果 driver 根据 JDBC URL 判断不是自己可以处理的连接就直接返回空,DriverManager 就是基于这个约定直到找到第一个不返回 null 的连接为止。

总结:本文聊了聊 SPI 的设计思想,分析了 Java SPI 的机制与 ServiceLoader 服务加载的实现原理,并且借 JDBC API 为例看了具体的应用。可以发现,Java 提供了一种不依赖额外框架的标准的本地服务发现机制,我们可以基于这个机制让我们的应用根据灵活性和可扩展性,在服务端基础库或者是安卓生态下都可以看到它的影子。只不过除了本地的服务发现之外,应该选择哪一个服务实现仍然是在设计扩展功能时需要额外考虑的部分。

总之了解 Java SPI 更重要的是学习 SPI 设计思想的一种实践。实际上你完全可以实现一个自己版本的 ServiceLoader(根据具体情况定义一个自己的服务发现机制,甚至 Jar 包也完全可以是通过远程加载的)来为你的应用提供一个独特的可扩展机制。
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
成都地铁四号线开通了吗 CS 75plus下雨天会自动关闭车窗吗 老人去世后名下的基金怎么办? 汽车发动机加多少机油? 歇后语:马尾拴豆腐-() 详谈新买紫砂壶怎么开壶 紫砂壶开壶之水煮法步骤详解 紫砂壶开壶方法——水煮法 开壶要煮多长时间紫砂壶开壶煮多久 第一次考注会报什么科目好 ...看看我家狗狗到底是泰迪还是贵宾啊?每次去宠物店美容说法都不一样... ...基本功能(不属于jdbc驱动程序必须实现的主要接口) 泰国钱100长什么样 怎么把wps文件的大小压缩一下 制作猪棒骨时需要掌握哪些基本的厨艺技巧? 裸眼视力低于4.8,能带眼镜参加体检吗? 公务员体检租ok镜,一天能达到4.8吗 我报了新疆监狱系统,视力要求裸眼4.8,可是我有只眼睛近视,体检怎样才能... 快速了解六大基酒之伏特加 伏特加革命背景 伏特加是什么酒解析伏特加的历史和制作工艺? 70层大秘境必出太古(70层大秘境不出太古) 苹果笔记本用什么软件可以歌曲中的原唱去掉 etc过高速多长时间扣费 跑完高速etc多久出账单 糖尿病人能吃桃子吗含糖量多少 "你儿子是我儿子的爸爸,那我是你的谁?" 你的儿子是我的儿子的爸爸,那我是你的谁 你的儿子是我的儿子的爸爸,那么我是你的谁。说出理由 你的儿子是我的儿子的爸爸,那你是我的谁? 第一次用JTA,报错,求帮助 快手公众怎么挂字 巴蒂尔拿过总冠军吗 巴蒂尔拿过几个总冠军 ...我可以申办哪个银行的信用卡,开卡最大金额为多少? pdf格式的转换成word后还是图片怎么办pdf转换成docx后还是图片 真正掌控生活过日子有规划的星座 不适合当情侣的12星座男女 欲哭无泪失败都是因为自己的四大星座 围棋是哪国发明的,有多少年的历史 钢筋符号打出来教程 张韶涵的《潘朵拉》歌词 怎样在WORD中输入钢筋符号 1.2.6.7 24点怎么算 说出几种! 1,2,6,7算24点,越多越好!急!急!急! 用1,2,6,7算24点 孕妇可以喝苏打水饮品吗 iOS中必备的5款APP神器,iPhone资深用户推荐! 有什么好的iOS影视软件推荐呢? ios记账软件哪个好用 宏迪大酒店酒店介绍