百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

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”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对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)...

取消回复欢迎 发表评论: