SPI机制详细讲解

news2024/12/30 2:58:47

文章目录

  • SPI机制
  • 案例分析
    • 建立DriverManager
    • 建立MysqlDriver来实现扩展
    • 建立OracleDriver来实现扩展
    • 测试spitest
  • 源码分析
    • ServiceLoader类的结构
    • reload加载类
    • LazyIterator类
    • parse解析URL对象方法
    • parseLine方法

SPI机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的
META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了
可能,比如在Springboot,Dubbo、JDBC中都使用到了SPI机制。

案例分析

以驱动加载为例。分别定义不同的实现,根据调用方引入不同的实现来进行加载具体的实现类。

建立DriverManager

定义一个获取驱动连接信息的接口。

/**
 * 公共的接口 com.elite.common.DriverManager
 */
public interface DriverManager {
    //获取连接信息
    String getConnectionInfo();
}

建立MysqlDriver来实现扩展

/**
 * SPI:MySQL对于 getConnectionInfo 的一种实现
 *
 */
public class MysqlDriver implements DriverManager
{

    @Override
    public String getConnectionInfo() {
        return "this is mysqldriver";
    }
}

此时我们需要在ClassPath下新建META-INF/services,新建一个名称必须是 定义的接口的全类路径,文件中写上接口的实现类的全类路径名称
配置文件

建立OracleDriver来实现扩展

/**
 * 扩展实现  com.elite.oracle.OracleDriver
 * com.elite.common.DriverManager
 */
public class OracleDriver implements DriverManager {
    @Override
    public String getConnectionInfo() {
        return "这是oracle数据库连接的扩展实现";
    }
}

oracle

测试spitest

需要在pom.xml引入具体的实现

        <dependency>
            <groupId>com.elite</groupId>
            <artifactId>MysqlDriver</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>com.elite</groupId>-->
<!--            <artifactId>OracleDriver</artifactId>-->
<!--            <version>1.0-SNAPSHOT</version>-->
<!--        </dependency>-->
<!--    </dependencies>-->

测试代码

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<DriverManager> providers = ServiceLoader.load(DriverManager.class);
        Iterator<DriverManager> iterator = providers.iterator();
        while(iterator.hasNext()){
            DriverManager next = iterator.next();
            String connectionInfo = next.getConnectionInfo();
            System.out.println(connectionInfo);
        }
    }
}

测试结果

源码分析

ServiceLoader类的结构

  //定义了配置文件的路径,这就是为何需要新建配置文件
  private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    //加载的服务或者接口
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    //类加载器
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    //ServiceLoader创建时的访问控制上下文
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    //缓存加载的服务,根据实例化的顺序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    //加载服务的类
    private LazyIterator lookupIterator;

reload加载类

  //加载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();
    }

LazyIterator类

1.加载配置文件
2.解析配置文件
3.解析每一行
4.获取配置的实现类的类名加入list
5.利用反射加载 c = Class.forName(cn, false, loader);
6.转换为具体的class对象添加到providers S p = service.cast(c.newInstance()); providers.put(cn, p);

 private class LazyIterator implements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        //URL对象
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //加载完全类限定名 META-INF/servie+接口类名
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        //通过类加载转换为URL对象
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //parse加载对应的服务
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
        //next方法
        private S nextService() {
            if (!hasNextService())= throw new NoSuchElementException();
            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(); // This cannot happen
        }
    }

parse解析URL对象方法

解析URL对象

private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError {
        InputStream in = null;
        BufferedReader r = null;
        //解析文件的配置实现类名list
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

parseLine方法

从配置文件解析单行,添加实现类的明名字到list

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)
        throws IOException, ServiceConfigurationError {
        //读取行数据
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        //判断#的位置
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            //提供的服务不包含在添加
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/476942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bridge模式如何配置

Bridge模式案例&#xff08;一&#xff09; 基于Docker引擎启动Nginx WEB容器&#xff0c;默认以Bridge方式启动Docker容器&#xff0c;会动态DHCP给Docker容器分配IP、网关等信息&#xff0c;操作指令如下&#xff1a; 查看镜像列表 docker images#运行新的Nginx容器 dock…

ChatGPT实现代码解释

代码解释 新手程序员在入门之初&#xff0c;最好的学习路径就是直接阅读其他人的代码&#xff0c;从中学会别人是怎么写的&#xff0c;为什么这么写。过去&#xff0c;这个学习过程可能需要广泛阅读官方文档&#xff0c;在 GitHub issue 上提问&#xff0c;上 Stack Overflow …

内网渗透(六十)之AS-REP Roasting攻击

AS-REP Roasting攻击 AS-REP Roasting是一种针对用户账户进行离线爆破的攻击方式。但是该攻击方式使用上比较受限,因为其需要用户账户设置“不要求Kerberos预身份验证”选项。而该选项默认是没有勾选的。Kerberos域身份验证发生在Kerberos身份验证的第一阶段(AS_REQ&AS_…

手记系列之四 ----- 关于使用MySql的一些经验

前言 本篇文章主要介绍的关于本人在使用MySql记录笔记的一些使用方法和经验&#xff0c;温馨提示&#xff0c;本文有点长&#xff0c;约1.5w字&#xff0c;几十张图片&#xff0c;建议收藏查看。 一、MySql安装 下载地址:https://dev.mysql.com/downloads/ 在安装MySql之前&a…

我在VScode学Java(Java一维数组)

我的个人博客主页&#xff1a;如果\真能转义1️⃣说1️⃣的博客主页 关于Java基本语法学习---->可以参考我的这篇博客&#xff1a;(我在Vscode学Java) 我在VScode学Java(Java一维数组&#xff09; Java 一维数组 声明数组&#xff1a;先声明&#xff0c;后使用 动态分配内…

最长连续子序列---双指针

一、最长连续不重复子序列 核心思路&#xff1a; 遍历数组a中的每一个元素a[i], 对于每一个i&#xff0c;找到j使得双指针[j, i]维护的是以a[i]结尾的最长连续不重复子序列&#xff0c;长度为i - j 1, 将这一长度与r的较大者更新给r。 对于每一个i&#xff0c;如何确定…

XR技术在手术中的应用调研

虚拟现实、增强现实、混合现实等概念和技术是最近几年发展起来的&#xff0c;相信你对去年大火的元宇宙深有感触&#xff0c;元宇宙属于虚拟现实的技术范畴&#xff0c;头号玩家电影也让虚拟现实走进大众的视野中。早在2015年&#xff0c;笔者参加一次展会时就有接触&#xff0…

【数据库复习】第四章数据库恢复技术

一、事务 定义 一个数据库操作序列 一个不可分割的工作单位&#xff08;要么全做&#xff0c;要么不做&#xff09; 恢复和并发控制的基本单位 事务和程序比较 在关系数据库中&#xff0c;一个事务可以是一条或多条SQL语句,也可以包含一个或多个程序。 一个程序通常包含…

java倒序输出数字的方法

1.在输入框中输入一个整数&#xff0c;比如要输入“5”&#xff0c;需要输出倒序&#xff0c;可以使用数字键盘进行输入&#xff0c;也可以使用文本编辑器进行输入。 2.在命令行中输入“6”&#xff0c;如图所示。 3.选择一个字符串作为例子。使用字符串编辑器中的 reverse命令…

这个 Chrome 插件,让你的 ChatGPT 不再报错

ChatGPT的官网最近几天报错越来越频繁了&#xff0c;相信大家都发现了。 一旦你离开页面时间比较久&#xff0c;再度返回跟它进行对话&#xff0c;就会出现如下报错&#xff1a; 虽然这个报错信息以前也出现过&#xff0c;但现在的频率确实过高&#xff0c;对于每天需要使用 C…

“火灾不分昼夜,安全在我心中”——五一前厂房消防检查纪实

检查人员: Scott, Jason, Willson, Hanson 检查时间: 2023年4月28日 检查地点: 1厂房、2厂房室内外 检查内容: 一、室内外消火栓: 室内栓外观正常&#xff1b; 室外栓: 栓体防冻防尘套破损、遗失&#xff0c;消防栓缺少防撞保护&#xff1b; 按规定距离厂房外墙不宜小于5…

gitlab部署及整合Jenkins持续构建(四)sonarqube9.9安装和使用(一步一坑)

文章目录 postgresql13.0安装1、配置postgresql数据库2、进入postgresql创建数据库 代码质量管理平台--sonarqube安装1、前置依赖下载2、安装unzip并解压sonarqube并移动到/usr/local&#xff1a;3、修改sonarqube相应的配置4、新增用户&#xff0c;并将目录所属权赋予该用户&a…

回村准备结婚了~

小伙伴们大家好&#xff0c;我是阿秀。 这几天是一年一度的五一假期&#xff0c;前几年因为疫情的存在&#xff0c;很多人的五一假期都只能憋在家里&#xff0c;不知道今年各位有没有出去游玩的计划和打算&#xff1f; 昨晚刷朋友圈看到很多人都在晒行程的&#xff0c;有打算去…

【MATLAB数据处理实用案例详解(19)】——利用神经模糊控制实现对洗衣机的控制

目录 一、问题描述二、洗衣机模糊控制三、运行结果四、完整代码 一、问题描述 20世纪90年代初期&#xff0c;日本松下公司推出了神经模糊控制全自动洗衣机这种洗衣机能够自动判断衣物质地的软硬程度、衣量多少、脏污程度和性质等&#xff0c;应用神经模糊控制技术&#xff0c;…

JavaScript实现输入成绩,输出成绩等级的代码

以下为实现输入成绩&#xff0c;输出成绩等级&#xff08;switch语句&#xff09;的代码和运行截图 目录 前言 一、实现输入成绩&#xff0c;输出成绩等级 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录…

C#底层库--自定义进制转换器(可去除特殊字符,非Convert.ToString方式)

系列文章 C#底层库–程序日志记录类 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/124187709 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csd…

从南极到你家,易开得,一支“中国芯”的奇幻漂流

2023年的AWE&#xff0c;精彩程度比我想象中还要夸张&#xff01; 一方面是热度空前&#xff0c;现场人头攒动&#xff0c;有一家老中幼三代一起来观展的&#xff0c;有经销商、客户来问价采购的&#xff0c;还有行业媒体举着单反、手机、摄像机激情拍照的&#xff0c;让我们正…

4月30日第壹简报,星期日,农历三月十一

4月30日第壹简报&#xff0c;星期日&#xff0c;农历三月十一坚持阅读&#xff0c;静待花开1. 五一小长假首日全国铁路迎客流高峰&#xff0c;创铁路单日客流历史新高&#xff0c;旅游订单量较春节假期首日增长668%。2. 六大国有银行一季报业绩披露&#xff1a;共赚近3600亿元&…

chatgpt 数据相关应用论文策略简介

hatGPT等预训练大模型&#xff0c;一个核心能力就是经过海量语料的训练加上强化学习的引导&#xff0c;其具有强大的接近人类的文本生成能力。这个能力的一大用途&#xff0c;就是可以为我们生产数据或者标注数据&#xff0c;再基于这些数据训练我们自己的模型。 On the Feasi…

B/S 结构系统的 缓存机制(Cookie) 以及基于 cookie 机制实现 oa 十天免登录的功能

B/S 结构系统的 缓存机制(Cookie) 以及基于 cookie 机制实现 oa 十天免登录的功能 文章目录 B/S 结构系统的 缓存机制(Cookie) 以及基于 cookie 机制实现 oa 十天免登录的功能每博一文案1. Cookie 的概述2. session 与 Cookie 之间的联系&#xff1a;3. Cookie 的作用&#xff…