SPI:JDK 与 SpringBoot

news2024/11/19 5:17:24

浅谈Java SPI原理与其在JDBC、Flink中的应用

SPI VS API
API:由被调方提供的实现了某个完整功能的接口,主调方直接调用该接口来享用该功能,而无需关注该接口的具体实现。比如使用 JDK 的 InputStream#read 从文件系统中读取数据。
SPI:被调方(框架)提供的功能扩展点接口,主调方可实现这些接口与被调方(框架)进行互动。

MySQL 应用

JDBC 中的 DriverManager 中定义的模板代码:将 Driver 实现类的 Class 的加载工作委托给 SPI。

// From DriverManager.java

private static final String JDBC_DRIVERS_PROPERTY = "jdbc.drivers";

private static Connection getConnection(
    // 省略代码 ...

	// 通过 SPI 加载 Driver,而无需再通过 Class.forName("com.mysql.jdbc.Driver")
    ensureDriversInitialized();

    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

	// 「驱动实现类」的 Class 被加载后,就会通过「静态代码块」将当前驱动对象注册到 registeredDrivers 中,比如见后文的 MySQL 驱动类 FabricMySQLDriver.java 的实现
    for (DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if (isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }

	// 省略代码...
}

private static void ensureDriversInitialized() {
    if (driversInitialized) {
        return;
    }

    synchronized (lockForInitDrivers) {
        if (driversInitialized) {
            return;
        }
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty(JDBC_DRIVERS_PROPERTY);
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try {
                    while (driversIterator.hasNext()) {
	                    // SPI 的 iterator 是懒加载,全部遍历后才能确保「驱动实现类」的 Class 已经通过 Class.forName 完成加载
                        driversIterator.next();
                    }
                } catch (Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers != null && !drivers.equals("")) {
            String[] driversList = drivers.split(":");
            println("number of Drivers:" + driversList.length);
            for (String aDriver : driversList) {
                try {
                    println("DriverManager.Initialize: loading " + aDriver);
                    Class.forName(aDriver, true,
                            ClassLoader.getSystemClassLoader());
                } catch (Exception ex) {
                    println("DriverManager.Initialize: load failed: " + ex);
                }
            }
        }

        driversInitialized = true;
        println("JDBC DriverManager initialized");
    }
}

SPI 会通过 Class.forName 形式加载 Class 文件

// From ServiceLoader.java : ServiceLoader.LazyClassPathLookupIterator

private Class<?> nextProviderClass() {
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null) {
                configs = ClassLoader.getSystemResources(fullName);
            } else if (loader == ClassLoaders.platformClassLoader()) {
                // The platform classloader doesn't have a class path,
                // but the boot loader might.
                if (BootLoader.hasClassPath()) {
                    configs = BootLoader.findResources(fullName);
                } else {
                    configs = Collections.emptyEnumeration();
                }
            } else {
                configs = loader.getResources(fullName);
            }
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return null;
        }
        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;
    }
}

mysql-connector-java SPI 实现
在这里插入图片描述

// From com.mysql.fabric.jdbc.FabricMySQLDriver.java

public class FabricMySQLDriver extends NonRegisteringDriver implements Driver {
    // may be extended to support other protocols in the future
    public static final String FABRIC_URL_PREFIX = "jdbc:mysql:fabric://";

    // 省略代码 ...

    // Register ourselves with the DriverManager
    static {
        try {
            DriverManager.registerDriver(new FabricMySQLDriver());
        } catch (SQLException ex) {
            throw new RuntimeException("Can't register driver", ex);
        }
    }
    // 省略代码 ...
}

深入剖析Spring Boot 的SPI机制

- META-INF/services/以「接口全限定名」命名的文件
- ServiceLoader#load

虽然java提供的SPI机制的思想非常好,但是也存在相应的弊端。

  1. 接口实现类只能通过程序员手动遍历来确认;
  2. 服务提供接口必须放到 META-INF/services/ 目录下。

针对java的spi存在的问题,Spring的SPI机制沿用的SPI的思想,但对其进行扩展和优化。Spring采用 spring.factories 方式实现SPI机制,有极强的扩展性。
Java SPI 机制每个服务提供接口都在 META-INF/services/ 目录下分别对应一个配置文件,配置文件中存放当前接口的所有实现类;Spring SPI 机制则在 META-INF/spring.factories 配置文件存放多个接口及对应的实现类:以接口全限定名作为key,实现类作为value来配置,多个实现类用逗号隔开。

SpringBoot 实现「自动装配」的原理就是通过 @EnableAutoConfiguration 执行了 SpringBoot 的 SPI 机制,加载、解析 ClassPath 路径下的所有 spring.factories 文件来实现的。

// From SpringFactoriesLoader.java

/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files.
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	// ...
	try {
		Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				String[] factoryImplementationNames =
						StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
				for (String factoryImplementationName : factoryImplementationNames) {
					result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
							.add(factoryImplementationName.trim());
				}
			}
		}
		// ...
	return result;
}

META-INF/spring.factories 文件可以存在多个 JAR 文件中,基于这个「约定」,可以围绕 Spring 生态做许多扩展。

其它

  • Java SPI META-INF/services 详解

API (Application Programming Interface)在大多数情况下,都是实现方制定接口协议并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

  • Java Service Provider Interface:SPI 使用案例

The Service Provider is installed in the form of extensions, a jar file which we place in the application classpath, the Java extension classpath or the user-defined classpath.

  • what’s the java classpath

classpaths contain:JAR files, and Paths to the top of package hierarchies.
how to set the classpath:1)通过操作系统环境变量设置;2) 通过 JVM 参数设置:java -cp parameter

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

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

相关文章

DOM 型 XSS 攻击演示(附链接)

一、介绍 DOM&#xff08;Document Object Model&#xff09;型 XSS&#xff08;Cross-Site Scripting&#xff09;攻击是一种 Web 应用程序中的安全漏洞&#xff0c;其特点是攻击者成功地注入了恶意脚本&#xff0c;这些脚本在用户的浏览器中执行&#xff0c;从而导致恶意行为…

DETR解读,将Transformer带入CV

论文出处 [2005.12872] End-to-End Object Detection with Transformers (arxiv.org) 一个前置知识 匈牙利算法&#xff1a;来源于二部图匹配&#xff0c;计算最小或最大匹配 算法操作&#xff1a;在n*n的矩阵中 减去行列最小值&#xff0c;更新矩阵&#xff08;此时行或者…

C# RichTextBox常用属性、方法学习1

1 字体 Font font1 new Font("宋体", 18); richTextBox1.Font font1; Font font2 new Font("宋体", 10, FontStyle.Underline); richTextBox1.SelectionFont font2; 定义字体&#xff0c;可以带2个参数&#…

代码随想录刷题笔记-Day13

1. 二叉树的层序遍历 102. 二叉树的层序遍历https://leetcode.cn/problems/binary-tree-level-order-traversal/层次遍历依靠队列的先进先出特点实现。 解题思路 层序遍历的本质就是对每一个pop出来的处理节点&#xff0c;处理后把他的左右节点放进去。 对于每一层来说&…

菱形打印和十进制ip转二进制

1.菱形打印 用for循环 #!/bin/bashread -p "请输入菱形的大小&#xff1a;" num #打印向上的等腰三角形 for ((i1;i<num;i)) dofor ((jnum-1;j>i;j--))doecho -n " " #打印的是前面的空格donefor ((k1;k<2*i-1;k))doecho -n "*" #打印…

【stm32】hal库学习笔记-FSMC连接TFT_LCD

【stm32】hal库学习笔记-FSMC连接TFT LCD 触摸屏结构与原理 LCD模块接口原理图 LCD 接口连接在 FSMC 总线上面&#xff0c;图中的 T_MISO/T_MOSI/T_PEN/T_SCK/T_CS 连接在 MCU 的 PB2/PF11/PB1/PB0/PC13 上&#xff0c;这些信号用来实现对液晶触摸屏的控制&#xff08;支持电阻…

QT tcp与udp网络通信以及定时器的使用 (7)

QT tcp与udp网络通信以及定时器的使用 文章目录 QT tcp与udp网络通信以及定时器的使用1、QT网络与通信简单介绍2、QT TCP通信1、 服务器的流程2、 客户端的流程3、服务器的编写4、客户端的编写 3、QT UDP通信1、客户端流程2、客户端编写3、UDP广播4、UDP组播 4、定时器的用法1、…

leetcode刷题日志-146LRU缓存

思路&#xff1a;使用hashmap储存key&#xff0c;vaule&#xff0c;使用双向链表以快速查到尾结点&#xff08;待逐出的节点&#xff09;&#xff0c;链表的题一定要在纸上画一下&#xff0c;不然连着连着就不知道连在哪里去了 class LRUCache {public class ListNode {int ke…

【js基础】日期对象的使用,查找、增加、克隆、删除DOM节点,M端事件

文章目录 前言一、日期对象日期对象的作用1.1 实例化1.2 日期对象的方法1.3 时间的格式化1.4 时间戳的使用时间戳是什么js的时间戳 二、DOM的增删改查什么叫做DOM节点2.1 DOM的查找2.2 增加节点2.3 克隆节点和删除节点 三、M端事件3.1 M端是什么&#xff1f; 总结 前言 在 Jav…

你的MiniFilter安全吗?

简介 筛选器管理器 (FltMgr.sys)是Windows系统提供的内核模式驱动程序, 用于实现和公开文件系统筛选器驱动程序中通常所需的功能; 第三方文件系统筛选器开发人员可以使用FltMgr的功能可以更加简单的编写文件过滤驱动, 这种驱动我们通常称为MiniFilter, 下面是MiniFilter的基本…

STM32CubeMX教程31 USB_DEVICE - HID外设_模拟键盘或鼠标

目录 1、准备材料 2、实验目标 3、模拟鼠标实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、设初始化调用流程 3.2.2、外设中…

优选6款前端动画特效分享(附在线演示)

优选6款前端动画特效 其中有CSS动画、canvas动画、js小游戏等等 下方效果图可能不是特别的生动 那么你可以点击在线预览进行查看相应的动画特效 同时也是可以下载该资源的 翻页时钟特效 基于react实现的一款翻页时钟特效 切换时间特效跟比赛切换分数牌相似 以下效果图只能体现…

Gitlab7.14 中文版安装教程

Gitlab7.14 中文版安装教程 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-01-28csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注 …

T05垃圾收集算法与垃圾收集器ParNew CMS

垃圾收集算法与垃圾收集器ParNew & CMS 垃圾收集算法 #### f 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法。根据对象存活周期不同将内存分为几块&#xff0c;一般将java堆分为新生代和老年代&#xff0c;然后根据各个年代的特点选择不同的垃圾收集算法。 在新…

MySQL-窗口函数 简单易懂

窗口函数 考查知识点&#xff1a; • 如何用窗口函数解决排名问题、Top N问题、前百分之N问题、累计问题、每组内比较问题、连续问题。 什么是窗口函数 窗口函数也叫作OLAP&#xff08;Online Analytical Processing&#xff0c;联机分析处理&#xff09;函数&#xff0c;可…

ESP8266 控制之 : 使用 RingBuffer USART1 和 USART3互传

简介 使用Buffer来避免数据的丢失, 或许你自己在使用串口进行收发时会丢失数据, 现在我们就来简单使用一下RingBuffer创建Rx、Tx的Buffer来避免发送接收丢包或数据丢失问题。 扩展知识 RingBuffer的介绍, 看完大概也就知道了&#xff0c;实在不知道就看看下面的代码 线路连接…

微信小程序开发学习笔记《14》上拉触底事件案例

微信小程序开发学习笔记《14》上拉触底事件案例 博主正在学习微信小程序开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。建议仔细阅读对应官方文档 一、最终实现效果 实现在加载完这一页之后&#xff0c;刷新之后展示 “数据加载中”&#xff0c;加载完后…

超温报警器电路设计方案汇总

超温报警器电路设计方案&#xff08;一&#xff09; 该超温报警电路由温度采集电路、继电器控制电路、延时电路、秒脉冲信号发生器、计数译码电路、数显电路、报警电路共同构成。下面来详细介绍一下各部分电路的功能。 温度采集电路 温度采集电路由负温度系数的热敏电阻RW、R…

STM32 自学笔记 学习笔记 一

起源&#xff0c;A7,A9,M3&#xff0c;原来弄了A9的TQ2440&#xff0c;结果还得来重新熟悉下32函数JLINK使用SW方式&#xff0c;本来可以下载&#xff0c;但是一根线掉了重新上去&#xff0c;就出各种跟线无关问题&#xff0c;干脆把32断了重新接&#xff0c;结果就成功了&…

服务攻防-开发组件安全JacksonFastJson各版本XStreamCVE环境复现

知识点 1、J2EE-组件Jackson-本地demo&CVE&#xff08;数据处理&#xff09; 2、J2EE-组件FastJson-本地demo&CVE&#xff08;数据处理&#xff09; 3、J2EE-组件XStream-本地demo&CVE&#xff08;数据处理&#xff09; 章节点&#xff1a; 1、目标判断-端口扫描…