SPI(Service Provider Interface)

news2025/1/11 14:56:46

ServiceLoad中的spi

1、简介
  • JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制。
2、JDBC举例
2.1、引入mysql依赖jar
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

2、前置了解:
  • JDBC:Java DataBase Connectivity,是sun公司提供的一套操作数据库的标准规范
  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口(规范)
  • 这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现(mysql、Oracle)
  • mysql的Driver也需要实现这个接口,以下是mysql的具体实现类
package com.mysql.jdbc;
import java.sql.DriverManager;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
  • mysql的jar中把具体实现的类Driver,放在META-INF/services中接口全路径file即java.sql.Driver中,内容为具体的实现类全路径com.mysql.jdbc.Driver

2.3、数据库连接过程(这里重点分析一中的spi)
        String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";  //定义连接数据库的url
        //一、获取mysql的数据库连接对象
        Connection conn = DriverManager.getConnection(url);
        //二、获取SQL语句执行对象
        Statement statement = conn.createStatement();
        //三、执行SQL语句
        int result = statement.executeUpdate("sql语句");
2.4、分析过程

将服务中所有.jar包下META-INF/services/java.sql.Driver文件中的实现类(eg:mysql的com.mysql.jdbc.Driver)加载到内存list中,并在getConnection中从list中获取实例对象

public class DriverManager {
    static {
        loadInitialDrivers();
    }
    
    public static Connection getConnection(String url)
        ///
    }
 }

2.4.1、通过spi,获取所有Driver接口实现类对象,放入list

1)、DriverManager.getConnection(url),因为getConnection是静态方法,会触发DriverManager类的首次主动使用,会调用()即调用static{}中loadInitialDrivers

private static void loadInitialDrivers() {
        // 一、load
		ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        // 二、hasNext
         while(driversIterator.hasNext()) {
             // 三、next
             driversIterator.next();
         }
  }

2)、load():指定上下文类加载器加载作为Driver接口的具体实现类com.mysql.jdbc.Driver的加载器

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();//上下文加载器,去加载Deiver接口的实现类
        return ServiceLoader.load(service, cl);
    }
打破双亲委派机制:

SPI接口实现类的加载需要破坏双亲委派模型。

  • java.sql.Driver是由根类加载器加载(因为其在rt.jar下)

  • 而不同厂商具体的实现(mysql、Oracle)的jar放在classpath(com|META-INF)下,根类加载器无法加载classpath路径下的类

  • 通过Thread.currentThread().setContextClassLoader,设置系统类加载器来加载(线程上下文中默认存放的系统类加载器)

3)、hasNext():按行读取所有.jar中含有"META-INF/services/com.sql.Driver"的file文件内容,将实现类全路径加入Iterator

        private boolean hasNextService() {
            //1.首次加载为null
            if (configs == null) {
                    // 2.这里的fullName = "META-INF/services/" + com.sql.Driver
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 3.获取url(不重要)
                        configs = loader.getResources(fullName);
               
            }
            while ((pending == null) || !pending.hasNext()) {
                // 4.会读取所有.jar,含有"META-INF/services/com.sql.Driver"的file文件
                // 一行一行的读取file的内容,比如读取com.msql.jdbc.Driver,将Diver接口所有的实现类的全路径放Iterator
                Iterator<String> pending = parse(service, configs.nextElement());//parseLine(service,names)
            }
            nextName = pending.next();
            return true;
        }

4)、next():创建Driver接口实现类的实例对象,并将实例对象放入list,便于后续getConnection时从list中获取实例对象

      private S nextService() {
			// 1.Diver接口实现类的全路径,比如com.mysql.jdbc.Driver
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 2.使用上下文加载器,创建class(com.mysql.jdbc.Driver)类对象
                c = Class.forName(cn, false, loader);
            } 
            
            try {
                // 3.生成Driver实现类的实例对象(重要)
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } 
        }


// 这里特别重要的一个点是c.newInstance(),即对实现类Diver创建实例
根据类的首次主动使用原则,会触发com.mysql.jdbc.Driver类的static{}
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } 
    }
}

    
//registerDriver方法,会将com.mysql.jdbc.Driver实例,加入list中,便于后续getConnection的获取
public static synchronized void registerDriver(java.sql.Driver driver){

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            //CopyOnWriteArrayList<DriverInfo> registeredDrivers 
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } 
}
  1. 4.2 getConnection
    public static Connection getConnection(String url)
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    
        // CopyOnWriteArrayList<DriverInfo> registeredDrivers
        // 这里遍历上述2.4.1中存入list的所有Driver接口的实现类对象,然后connect,这里以com.mysql.jdbc.Driver的connect
        // 为例
        for(DriverInfo aDriver : registeredDrivers) {
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
               println("getConnection returning " + aDriver.driver.getClass().getName());
               return (con);
             }      
 }
public class NoRegisterDriver{
   public Connection connect(String url, Properties info) throws SQLException {
        
        // 1.一般我们都会使用.properties将数据库连接的url、port、user、password等属性放在文件中使用Properties加载
        Properties props = null;
         // 2.创建mysql的数据库连接(localhost即ip地址信息、port端口信息等,都可以从String url = "jdbc:mysql://localhost:3306/test?user=root&password=root"中解析)
        com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
                return newConn;
            }
        }
    }
}


    protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, 				String databaseToConnectTo, String url)  {
        // 底层还是通过反射,Constructor ctor.newInstance(args)创建的JDBC4对应的mysql的con对象
    }     

至此,就得到了mysql数据库的con连接对象

3、自定义操作

3.1 定义client模块,内含自定义接口Myservice以及其实现类MyServiceImpl

public interface MyService {
    public void show();
}




public class MyServiceImpl implements MyService {
    @Override
    public void show() {
        System.out.println("load MyServiceImpl spi");
    }
}

3.2 将自定义接口实现类全路径,作为内容写在清单文件file(自定义接口全路径)中

  • 创建清单文件:resources/META-INF/services/创建file,file的名称为自定义接口的全路径:com.mjp.service.MyService
  • file清单文件内容自定义接口实现类的全路径:com.mjp.service.impl.MyServiceImpl(可以在多行写多个实现类)
  • install模块client生成jar,后续在service中pom引入此jar

3.3 定义service模块

  • pom中依赖client模块
    <dependencies>
        <dependency>
            <groupId>CodeBetter</groupId>
            <artifactId>com.mjp.client</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
  • 编写ServiceLoader
    public static void main(String[] args) {
        ServiceLoader<MyService> services = ServiceLoader.load(MyService.class);
        for (MyService service : services) {
            service.show();//load MyServiceImpl spi
        }
    }

在这里插入图片描述

4、作用

spi服务发现机制,更具有插拔性。

  • 每当接口有新的实现类时,只要实现类遵循file文件的存放目录以及命名和内容,则ServiceLoader.load就帮你把所有的实现类都加载到ServiceLoader<接口> loadedServices中,程序员无需感知,可以直接在代码中使用新增的实现类对象,而不是在硬编程的方式,自己再new实现类对象。

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

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

相关文章

[IntelliJ IDEA] 中Lombok插件的介绍和常见使用情景

文章目录 介绍使用Lombok 介绍 在编写项目时&#xff0c;尤其是在类进行类内部成员字段封装时&#xff0c;需要编写大量的get/set方法&#xff0c;不仅写的麻烦&#xff0c;如果字段名发生改变就要进行修改&#xff0c;因此非常麻烦&#xff1b;因此使用Lombok就能解决这样的问…

C++的vector使用

vector 1.vector的介绍2.vector的使用2.1. vector的定义&#xff08;构造函数&#xff09;2.2. vector iterator的使用2.3. vector空间增长问题2.4. vector的增删改查vector 迭代器失效问题&#xff08;重点&#xff09; 1.vector的介绍 vector是和数组类似的序列容器&#xf…

一文全览机器学习建模流程(Python代码)

注&#xff1a;本文基于之前的文章做了些修改&#xff0c;重复部分可以跳过看。示例的项目为基于LR模型对癌细胞分类的任务。 随着人工智能时代的到来&#xff0c;机器学习已成为解决问题的关键工具&#xff0c;如识别交易是否欺诈、预测降雨量、新闻分类、产品营销推荐。我们接…

深入浅出C语言——字符/字符串操作函数

文章目录 一、字符串操作函数1. strlen2. strcpy3. strcat4. strcmp5. strncpy6. strncat7. strncmp8. strstr9. strtok10. strerror二、字符操作函数 一、字符串操作函数 1. strlen size_t strlen ( const char * str );strlen 获取字符串长度。字符串已经 ‘\0’ 作为结束标…

Mybatis二级缓存详解

目录 二级缓存 MyBatis的缓存机制整体设计以及二级缓存的工作模式 二级缓存的划分 使用二级缓存&#xff0c;必须要具备的条件 一级缓存和二级缓存的使用顺序 二级缓存实现的选择 MyBatis自身提供的二级缓存的实现 二级缓存的作用 二级缓存的作用域 二级缓存应用场景…

Pruning 系列 (六)remove剪枝

环境 python 3.9numpy 1.24.1pytorch 2.0.0+cu117一、填0与remove剪枝的差异 直接填0的剪枝: 优点: 保留了原始网络结构,便于实现和微调。部分减少模型的计算量。缺点: 零权重仍然需要存储,因此不会减少内存使用。一些硬件和软件无法利用稀疏计算,从而无法提高计算效率…

Linux网络——shell编程之awk编辑器

Linux网络——shell编程之awk编辑器 一、awk编辑器1.概述2.工作流程3.常用选项 二、awk的基础用法1.输出文件中的某一列2.根据特定条件筛选数据3.按照分隔符进行切割4.在匹配到特定字符串时执行操作5.BEGIN打印模式6.awk的分隔符用法 三、示例演示1.获取本机上一次开机时间2.检…

Unity UI -- (1)概览

UI&#xff08;User Interface&#xff0c;用户接口&#xff09;&#xff0c;是让用户和计算机或计算机应用交互的接口系统。 在实时3D项目中&#xff0c;UI通常是由文本、按钮、复选框、滑动条和单选框等组合出来的界面。这些元素使用特定规则或通知和用户进行交互。 UI设计是…

路由信息协议(RIP)

文章目录 1 概述2 RIP 要点2.1 根据 距离矢量&#xff08;或 跳数&#xff09;寻找最佳路由2.2 RIP 三大要点2.3 基本工作过程2.4 路由条目的更新规则2.5 RIPv1 和 RIPv22.6 "坏消息传播慢" 的问题 3 网工软考真题 1 概述 #mermaid-svg-bIbiI8QAnQj8HX7d {font-famil…

Vivado综合属性系列之四 ROM_STYLE

目录 一、前言 二、ROM_STYLE 一、前言 ROM英文全称为Read Only Memory&#xff0c;只读存储器&#xff0c;里面主要存放固定的数据。 二、ROM_STYLE ROM的使用方式与RAM类似&#xff0c;格式样例为&#xff1a;(* rom_style"{distributed | block}" *)&#xff0…

C++类和对象入门

C类和对象入门 1. 面向过程和面向对象初步认识2. 类的引入3. 类的定义3.1 类的两种定义方式 4.类的访问限定符及封装4.1 访问限定符说明 5. 封装5. 类的作用域6. 类的实例化7. 类对象模型7.1 类对象存储规则7.2 如何计算类对象的大小 8. this指针总结 1. 面向过程和面向对象初步…

简述 JavaScript 被执行的那些事情

简述 JavaScript 被执行的那些事情 JavaScript 是脚本语言 JavaScript 是解释型语言 JavaScript 执行流程 不同浏览器有不同的 JavaScript 引擎&#xff08;运行时&#xff09;&#xff0c;主流的 JS 引擎有&#xff1a; V8&#xff0c;chromium 内核的引擎&#xff0c;主…

【ChatGPT4.0+PPT】使用NewBing搭配mindshow快速完成PPT任务

使用NewBing搭配mindshow快速完成PPT任务 前言一、使用NewBing生成PPT大纲二、粘贴Markdown到mindshow生成PPT三、结果展示四、扩展 前言 完成一个简单的PPT只需要两步&#xff1a; 使用NewBing生成PPT大纲粘贴Markdown到mindshow生成PPT 如果访问不了NewBing可以用国内Chat…

Minecraft外置登录服务器Little-Skin讲解

随着Minecraft的普及与发展&#xff0c;现在许多小伙伴是越来越爱上了MC。但是想玩国际正版岂是那么容易&#xff1f;或许你会说“HMCL不就是java启动器吗&#xff1f;”&#xff0c;这句话是没错&#xff0c;但是HMCL的都是离线账户。那么该怎么永远拥有一个稳定的账户呢&…

JVM 垃圾回收相关算法

垃圾标记阶段 对象存活判断: 在堆里存放着几乎所有的 Java 对象实例&#xff0c;在GC 执行垃圾回收之前&#xff0c;首先需要区分出内存中哪些是存活对象&#xff0c;哪些是已经死亡的对象。只有被标记为已经死亡的对象&#xff0c;GC 才会在执行垃圾回收时&#xff0c;释放掉…

简单介绍Qt Quick、QML以及Qt Widgets之间的区别与联系

作者&#xff1a;CCAccept 专栏&#xff1a;Qt Quick 文章目录 前言Qt WidgetsQt Widgets的发展Qt Widgets的优点Qt Widgets的应用场景 QML与Qt QuickQML的发展QML的优点QML的应用场景 总结 前言 之前都是一直在做Qt Widgets的项目&#xff0c;最近由于实验室要求&#xff0…

从零开始,教你如何写出好的品牌策划推广方案

要写出一份好的品牌策划推广方案其实并不难&#xff0c;道叔今天给你分享自己十年来在品牌策划行业内的一个经验干货&#xff0c;希望对你有所帮助。 首先&#xff0c;你要知道的是一份“合格”品牌策划方案需要必备的三个条件&#xff1a; 1、逻辑 品牌策划的逻辑决定了方案…

Vue实战笔记(三) 引入Quill Editor

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲如何在 Vue 中引入 Quill \text{Quill} Quill 1、背景介绍 在前端开发中&#xff0c;富文本编辑器是一个重要的功能组件&#xff0c;方便用户创建和编辑格式丰富的文本内容 目前在市面上有着很多富文本编辑器组件&#x…

chatgpt赋能Python-30_days_of_python

用20行Python写的入门级小游戏:“猜数字”游戏 如果你想了解Python编程语言并开始使用它&#xff0c;那么编写一个短小精简的小游戏是一个不错的起点。在这篇文章中&#xff0c;我们将介绍一个入门级小游戏&#xff1a;猜数字游戏。 该游戏旨在帮助你了解Python命令行输入输出…

QRegion 限制 QPainter 的绘制区域

我有这样一个需求。 有一张图片&#xff0c;这张图片上面被我用不同的颜色画了不同的区域&#xff0c;然后我想选择这张图片中的某一种颜色&#xff0c;只在这种颜色所在的区域内进行绘制或者用橡皮擦擦除这种颜色&#xff0c;而不会影响其他颜色。 看着这个需求的时候&#…