DUBBO系列(1)什么是SPI机制 dubbo spi原理
liebian365 2024-11-09 13:48 20 浏览 0 评论
请点击【关注】获取更多互联网和技术干货,头条号IT徐胖子原创本文请勿转载,感谢支持
1 文章概述
SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样可以在运行时动态为接口替换实现类,我们通过 SPI 机制可以为程序提供拓展功能。
本文我们介绍JDK SPI使用方法并通过分析源码深入理解。下一篇文章介绍Dubbo自己实现的SPI机制。
2 SPI实例分析
(1) 新建DataBaseDriver项目工程并定义接口DataBaseDriver
public interface DataBaseDriver {
String connect(String hostIp);
}
(2) 打包这个工程为JAR
<dependency>
<groupId>com.itxpz.spi</groupId>
<artifactId>DataBaseDriver</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
(3) 新建MySQLDriver工程添加上述依赖并实现DataBaseDriver接口
import com.itxpz.database.driver.DataBaseDriver;
public class MySQLDataBaseDriver implements DataBaseDriver {
@Override
public String connect(String hostIp) {
return "MySQL DataBase Driver connect";
}
}
(4) 在MySQLDriver项目新建文件
src/main/resources/META-INF/services/com.itxpz.database.driver.DataBaseDriver
(5) 在此文件添加如下内容
com.itxpz.database.mysql.driver.MySQLDataBaseDriver
(6) 新建OracleDriver工程操作方式相同,配置文件内容有所变化
com.itxpz.database.oracle.driver.OracleDataBaseDriver
(7) 将上述两个项目打包
<dependency>
<groupId>com.itxpz.spi</groupId>
<artifactId>MySQLDriver</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.itxpz.spi</groupId>
<artifactId>OracleDriver</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
(8) 新建测试项目引入上述MySQLDriver、OracleDriver依赖
public class DataBaseConnector {
public static void main(String[] args) {
ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
Iterator<DataBaseDriver> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
DataBaseDriver driver = iterator.next();
System.out.println(driver.connect("localhost"));
}
}
}
输出结果
MySQL DataBase Driver connect
Oracle DataBase Driver connect
我们并没有指定使用哪个驱动进行连接,而是通过ServiceLoader方式加载实现了DataBaseDriver接口的实现类。假设我们只想要使用MySQL驱动那么直接引入相应依赖即可。
3 源码分析
3.1 迭代器模式
我们在分析JDK SPI源码之前首先学习迭代器设计模式,因为JDK SPI应用了迭代器模式。
public class OrderInfoModel implements Serializable {
private String orderId;
public OrderInfoModel(String orderId) {
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
@Override
public String toString() {
return "OrderInfoModel [orderId=" + orderId + "]";
}
}
import java.util.Iterator;
import java.util.List;
import org.springframework.util.CollectionUtils;
public class OrderInfoIterator implements Iterator<OrderInfoModel> {
private int cursor;
private List<OrderInfoModel> orderInfoList;
public OrderInfoIterator(List<OrderInfoModel> orderInfoList) {
this.cursor = 0;
this.orderInfoList = orderInfoList;
}
@Override
public boolean hasNext() {
if(CollectionUtils.isEmpty(orderInfoList)) {
throw new RuntimeException("param error");
}
return cursor != orderInfoList.size();
}
@Override
public OrderInfoModel next() {
if(CollectionUtils.isEmpty(orderInfoList)) {
throw new RuntimeException("param error");
}
OrderInfoModel element = orderInfoList.get(cursor);
cursor++;
return element;
}
}
public class TestMain {
public static void main(String[] args) {
List<OrderInfoModel> orderInfoList = new ArrayList<>();
OrderInfoModel order1 = new OrderInfoModel("111");
OrderInfoModel order2 = new OrderInfoModel("222");
OrderInfoModel order3 = new OrderInfoModel("333");
orderInfoList.add(order1);
orderInfoList.add(order2);
orderInfoList.add(order3);
Iterator<OrderInfoModel> iterator = new OrderInfoIterator(orderInfoList);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
输出结果
OrderInfoModel [orderId=111]
OrderInfoModel [orderId=222]
OrderInfoModel [orderId=333]
3.2 SPI源码分析
public class DataBaseConnector {
public static void main(String[] args) {
// 根据类型获取服务加载器
ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
// 获取迭代器
Iterator<DataBaseDriver> iterator = serviceLoader.iterator();
// 迭代器遍历
while (iterator.hasNext()) {
DataBaseDriver driver = iterator.next();
System.out.println(driver.connect("localhost"));
}
}
}
进入ServiceLoader.load方法
ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
跟进load方法发现只是进行初始化
public final class ServiceLoader<S> implements Iterable<S> {
// 默认加载服务路径
private static final String PREFIX = "META-INF/services/";
// 缓存提供者信息
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 当前迭代器
private LazyIterator lookupIterator;
public void reload() {
// 清除缓存
providers.clear();
// 核心迭代器
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
}
进入serviceLoader.iterator()方法
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
// 如果缓存有则取缓存
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 缓存没有则重新加载
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
进入迭代器遍历代码
while (iterator.hasNext()) {
DataBaseDriver driver = iterator.next();
System.out.println(driver.connect("localhost"));
}
LazyIterator核心方法分析详见注释。核心是读取指定路径文件内容,通过反射进行类实例化并且保存至缓存容器。因为创建类需要使用栈空间,如果不使用缓存频繁创建类会造成栈溢出异常。
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// META-INFO/Services/com.itxpz.database.driver.DataBaseDriver
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 构建fullName路径配置对象
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析文件内容
pending = parse(service, configs.nextElement());
}
// com.itxpz.database.mysql.driver.MySQLDataBaseDriver
// com.itxpz.database.mysql.driver.OracleDataBaseDriver
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// com.itxpz.database.mysql.driver.MySQLDataBaseDriver
// com.itxpz.database.mysql.driver.OracleDataBaseDriver
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过反射进行实例化
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service, "Provider " + cn + " not a subtype");
}
try {
// 类型转换父类引用指向子类对象
S p = service.cast(c.newInstance());
// 保存至缓存容器
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated", x);
}
throw new Error();
}
}
4 SPI实际应用
使用JDBC时利用DriverManager加载数据库驱动时正是使用了SPI机制,我们引入MySQL依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
在MySQL依赖包中会发现如下文件
META-INF/services/java.sql.Driver
在使用DriverManager加载驱动时可以发现SPI机制
package java.sql;
public class DriverManager {
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// META-INF/services/java.sql.Driver
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
}
}
请点击【关注】获取更多互联网和技术干货,头条号IT徐胖子原创本文请勿转载,感谢支持
相关推荐
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
-
明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
-
首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
-
齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...
- Qt界面——搭配QCustomPlot(qt platform)
-
这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
-
老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
-
用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
-
前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...
- 掌握Visual Studio项目配置【基础篇】
-
1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
-
随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...
- Visual Studio Community 2022(VS2022)安装图文方法
-
直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...
- Qt添加MSVC构建套件的方法(qt添加c++11)
-
前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...
- Qt为什么站稳c++GUI的top1(qt c)
-
为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...
- qt开发IDE应该选择VS还是qt creator
-
如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...
- Qt 5.14.2超详细安装教程,不会来打我
-
Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...
- Cygwin配置与使用(四)——VI字体和颜色的配置
-
简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
- Qt界面——搭配QCustomPlot(qt platform)
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
- 掌握Visual Studio项目配置【基础篇】
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
- Visual Studio Community 2022(VS2022)安装图文方法
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)