Spring SPI

news2024/12/23 19:21:05

 SPI 服务供给接口(Service Provider Interface)。是Java 1.5新添加的一个内置标准,允许不同的开发者去实现某个特定的服务。

1 SPI 介绍

一个接口,可能会有许多个实现,我们在编写代码时希望能动态切换具体实现,例如:

Interface interface = new Implement1(); // 创建一个具体的interface

上面是硬编码方式,我们希望在不修改代码的情况下,更换interface的具体实现。当然我们可以使用配置文件方式来实现这个需求,伪代码如下:

ResourceBundle rb = ResourceBundle.getBundle(“interface.properties”);

String impName = rb.getString(“impName”);

Interface interface = (Interface) Class.forName(impName).newInstance();

SPI 的实现则类似于上面的方法。让系统找到具体的实现。

 1.1 SPI的使用

图 示例代码的项目结构说明

1)定义一个接口,在spi_example_interface项目中定义MakeMoney接口。

public interface MakeMoney {
    void hardWord();
}

2) 在自定义项目中实现接口,在spi_example_implement项目编写TeacherMakeMoney和ProgrammerMakeMoney两个类并实现MakeMoney接口。

public class ProgrammerMakeMoney implements MakeMoney {

    public ProgrammerMakeMoney() {
        System.out.println("程序员实例被创建了");
    }

    @Override
    public void hardWord() {
        System.out.println("敲代码");
    }
}

public class TeacherMakeMoney implements MakeMoney {

    public TeacherMakeMoney() {
        System.out.println("教师实例被创建了");
    }

    @Override
    public void hardWord() {
        System.out.println("教书");
    }
}

 3)在spi_example_implement项目中,resources文件下新建META-INF/services 文件夹,并在该文件夹下面创建由接口完全限定名命名的文件,在文件中依次列出该接口实现类的完全限定名。

图 接口实现类说明文件

4)使用定义的接口,利用Java提供的ServiceLoader类发现这个接口的实现,并使用它们。

public class SpiUse {
    public static void main(String[] args) {
        ServiceLoader<MakeMoney> makeMonies = ServiceLoader.load(MakeMoney.class);
        Iterator<MakeMoney> iterator = makeMonies.iterator();
        while (iterator.hasNext()) {
            MakeMoney imp = iterator.next(); // 实现被加载的系统
            imp.hardWord();
        }
    }
}
/*
运行结果:
程序员实例
敲代码
教师实例被创建了
教书
*/

1.2 java.sql.Driver与SPI

在Java中定义了接口java.sql.Driver,其并没有具体的实现,具体的实现都是由不同的厂商提供。下面将以mysql的驱动为例,来大致介绍Java如何管理JDBC服务。

1)实现java.sql.Driver接口。

图 mysql-connector-java jar包中Driver的定义

2) 在META-INFA/services文件夹下编写以Driver接口全限定名命名的文档,来引导ServiceLoader发现mysql实现的Driver的接口。

图 mysql jar包下的引导文件

3)注册并管理JDBC服务。

图 jdbc服务的调用过程

我们在使用jdbc 服务时,第一步是获取对数据库的连接,即执行上图的DriverManager.getConnection(url)方法。

图 DriverManager的getConnection()方法的部分代码块

以下代码是模拟数据库厂商实现java.sql.Driver这个接口:

定义SqlDriver接口,全限定名是 com.huangmingfu.SqlDriver:

public abstract class SqlDriver {

    private static List<SqlDriver> driverList = new ArrayList<>();

    /**
     * 执行sql
     */
    public abstract void execute(String sql);

    public abstract Boolean connect(String url);

    public static void register(SqlDriver sqlDriver) {
        driverList.add(sqlDriver);
    }

    public static SqlDriver getConnect(String url) {
        for (SqlDriver driver : driverList)
            if (driver.connect(url)) return driver;
        return null;
    }

}

第三方项目中对SqlDriver接口的实现(mysql和oracle)

public class MySqlDriver extends SqlDriver {

    public MySqlDriver() {
        System.out.println("MySqlDriver实例被创建");
    }

    static {
        System.out.println("MySqlDriver实例被创建被加载到虚拟机了,进行注册:SqlDriver.register");
        SqlDriver.register(new MySqlDriver());
    }

    @Override
    public void execute(String sql) {
        System.out.println("mysql数据驱动,执行sql:" + sql);
    }

    @Override
    public Boolean connect(String url) {
        return url.startsWith("mysql");
    }
}

public class OracleDriver extends SqlDriver {

    public OracleDriver() {
        System.out.println("OracleDriver 实例被创建");
    }

    static {
        System.out.println("OracleDriver实例被创建被加载到虚拟机了,进行注册:SqlDriver.register");
        SqlDriver.register(new OracleDriver());
    }

    @Override
    public void execute(String sql) {
        System.out.println("oracle数据驱动,执行sql:" + sql);
    }

    @Override
    public Boolean connect(String url) {
        return url.startsWith("oracle");
    }

}

 在第三方项目的META-INF/com.huangmingfu.SqlDriver 引导文件中写入实现类的全限定名:

com.custom.MySqlDriver
com.custom.OracleDriver

使用Driver的实现类,来获取数据库连接:

public class UserDriver {

    private static SqlDriver sqlDriver;

    public static void main(String[] args) throws Exception{
        System.out.println("项目启动....");
//        classForName();
        spi();
    }

    /**
     * 反射形式
     */
    private static void classForName() throws Exception {
        System.out.println("尝试先通过class.forName的形式");
        sqlDriver = (SqlDriver)Class.forName("com.custom.MySqlDriver").newInstance();
        sqlDriver.execute("SELECT VERSION();");
    }

    /**
     * spi形式
     */
    private static void spi() {
        ServiceLoader<SqlDriver> serviceLoader = ServiceLoader.load(SqlDriver.class);
        Iterator<SqlDriver> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) iterator.next(); //只是做加载动作
        SqlDriver driver = SqlDriver.getConnect("mysql://");
        if (driver != null) driver.execute("SELECT VERSION()");
    }
}
/*
运行结果
项目启动....
MySqlDriver实例被创建被加载到虚拟机了,进行注册:SqlDriver.register
MySqlDriver实例被创建
MySqlDriver实例被创建
OracleDriver实例被创建被加载到虚拟机了,进行注册:SqlDriver.register
OracleDriver 实例被创建
OracleDriver 实例被创建
mysql数据驱动,执行sql:SELECT VERSION()
 */

2 SPI 原理

java实现SPI的是ServiceLoader类,其实现步骤一共有两步:1)根据接口的全限定名查找META-INF/services下的接口实现引导文件记录的实现类全限定名集合;2)通过Class.forName(全限定名).newInstance()方法来将这些实现类加载进jvm中。

图 第一步ServiceLoader获取接口实现类的全限定名

图 第二步 ServiceLoader创建实现类的实例

3 SPI的优缺点及应用场景

spi 能扩展服务,将接口与实现解耦。通过服务接口和服务提供者,实现了服务规范的制定和服务具体实现的分离。

API

在大多数情况下,都是实现方制定接口并完成对接口的实现。调用方仅仅依赖接口调用,且无权选择不同实现。API是直接被应用开发人员使用。

SPI

是调用方来制定接口规范,提供给外部来实现。调用方在调用时则选择自己需要的外部实现。SPI是被框架扩展人员使用。

表 API与SPI的对比

缺点:

1)不能按需加载,需要遍历所有的实现并实例化,然后在循环中才能找到我们需要的实现。

2)多个并发多线程使用ServiceLoader类的实例是不安全的。

应用场景:

有关组织和公司定义接口标准,第三方提供具体实现。例如JDBC。

4 Spring Boot 中的spring.factories

在Spring Boot项目中,怎么将pom.xml文件里添加的依赖中的bean注册到Spring Boot项目的容器中呢?

在项目中,@ComponentScan注解只会扫描项目包内的bean并注册到Spring容器中,项目依赖包中的bean不会被扫描和注册。此时可以利用SPI来对这些依赖包中的bean进行加载注册。

META-INF/spring.factories 文件类似于SPI中的接口实现类引导文件。有spring-core包中的SpringFactoriesLoader类充当着类似ServiceLoader的作用。

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

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

相关文章

微服务测试怎么做

开发团队越来越多地选择微服务架构而不是单体结构&#xff0c;以提高应用程序的敏捷性、可扩展性和可维护性。随着决定切换到模块化软件架构——其中每个服务都是一个独立的单元&#xff0c;具有自己的逻辑和数据库&#xff0c;通过 API 与其他单元通信——需要新的测试策略和新…

关系代数、SQL语句和Go语言示例

近些年&#xff0c;数据库领域发展日新月异&#xff0c;除传统的关系型数据库外&#xff0c;还出现了许多新型的数据库&#xff0c;比如&#xff1a;以HBase、Cassandra、MongoDB为代表的NoSQL数据库&#xff0c;以InfluxDB、TDEngine为代表的时序数据[1]库&#xff0c;以Neo4J…

设计模式-代理模式-笔记

动机&#xff08;Motivation&#xff09; 在面向对象系统中&#xff0c;有些对象由于某种原因&#xff08;比如对象创建的开销很大&#xff0c;或者某些操作需要安全控制&#xff0c;或者需要远程外的访问等&#xff09;&#xff0c;直接访问会给使用者、或者系统结构带来很多…

【Linux网络】工作环境救急——关于yum安装的5个花式操作

目录 1、只下载不安装&#xff0c;离线安装软件 2、自行打包创建元数据 第一步&#xff1a;先准备好nginx的软件包&#xff0c;放在一个文件夹下 第二步&#xff1a;在本地下载createrepo命令软件&#xff0c;用于创建元信息&#xff0c;这个一定是对包的上一级目录使用命令…

cpolar+LightPicture,将个人电脑改造成公网图床服务器

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

QNX Typed memory介绍

文章目录 前言一、什么是 Typed memory二、查看系统已有Typed memory 的方法三、Typed memory 的使用方法1.定义一个packet memory并从系统内存中分出它1.1 as_add()1.2 as_add_containing()2. 从 Typed memory 中申请内存2.1 POSIX method 申请内存2.2 QNX Neutrino method 申…

第9章 K8s进阶篇-持久化存储入门

9.1 k8s存储Volumes介绍 Container&#xff08;容器&#xff09;中的磁盘文件是短暂的&#xff0c;当容器崩溃时&#xff0c;kubelet会重新启动容器&#xff0c;但最初的文件将丢失&#xff0c;Container会以最干净的状态启动。另外&#xff0c;当一个Pod运行多个Container时&…

Unity Quaternion接口API的常用方法解析_unity基础开发教程

Quaternion接口的常用方法 Quaternion.Euler()Quaternion.Lerp()Quaternion.Inverse()Quaternion.RotateTowards() Quaternion在Unity中是一种非常重要的数据类型&#xff0c;用于表示3D空间中的旋转。Quaternion可以表示任何旋转&#xff0c;无论是在哪个轴上旋转多少度&#…

C#asp.net考试系统+sqlserver

C#asp.net简易考试系统 sqlserver在线考试系统学生登陆 判断学生是否存在 选择课程名 科目 可以进行答题操作&#xff0c;已经考试的课程不能再次答题&#xff0c; 自动根据课程名对应的题库生成试卷界面 加入选项类容 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数…

Excel vlookup 如何使用

Excel vlookup 如何使用 打开WX, 搜索 “程序员奇点” Excel vlookup可以说是利器&#xff0c;非常好用的工具&#xff0c;用来查询 Excel 或者进行数据匹配&#xff0c;十分方便。 VLookuP 如何使用&#xff0c;不常用的同学经常容易忘记&#xff0c;这次做个记录&#xff…

2023.11.15 关于 Spring Boot 配置文件

目录 引言 Spring Boot 配置文件 properties 配置文件说明 基本语法 读取配置文件 优点 缺点 yml 配置文件说明 基本语法 读取配置文件 yml 配置不同数据数据类型及 null 字符串 加单双引号的区别 yml 配置 列表&#xff08;List&#xff09; 和 映射&#xff08;…

51单片机应用从零开始(四)

51单片机应用从零开始&#xff08;一&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;二&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;三&#xff09;-CSDN博客 详解 KEIL C51 软件的使用建立工程-CSDN博客 详解 KEIL C51 软件的使用设置工程编绎与连接程序…

SQL注入学习--GTFHub(布尔盲注+时间盲注+MySQL结构)

目录 布尔盲注 手工注入 笔记 Boolean注入 # 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 时间盲注 手工注入 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 MySQL结构 手工注入 sqlmap注入 笔记 union 联合注入&#xff0c;手工注入的一般步骤 …

python趣味编程-5分钟实现一个Flappy Bird游戏(含源码、步骤讲解)

Python 中的 Flappy Bird 游戏可以免费下载开源代码,它是为想要学习 Python 的初学者创建的。 该项目系统使用了 Pygame 和 Random 模块。 Pygame 是一组跨平台的 Python 模块,专为编写视频游戏而设计。 Python 中的 Flappy Bird 代码 – 项目信息 项目名称:Python 中的 Fl…

解决Jira导出csv最大限度是1000的问题

JIRA为了防止过多影响性能&#xff0c; 设置了导出CSV的上线为1000&#xff0c;影响了搜索结果导出以及RestAPI。 可以通过以下配置参数修改此限制&#xff1a; 通过JIRA管理界面的"高级设置 “设置以下参数 系统管理 > 系统 > 一般设置>高级设置找到 jira.sea…

qt Rectangle 使用Gradient设置渐变方向 制作渐变进度条

1.Gradient方向可查看官网 Gradient.Horizontala horizontal gradient Gradient.Verticala vertical gradient ​​ ProgressBar {id: batteryvalue: 0.5width: 150height: 20anchors.centerIn: parentbackground: Rectangle {implicitWidth: battery.widthimplicitHeight:…

Github小彩蛋显示自己的README,git 个人首页的 README,readme基本语法

先上效果&#x1f447; 代码在下面&#xff0c;流程我放最下面了&#xff0c;思路就是创建一个和自己同名的仓库&#xff0c;要公开&#xff0c;创建的时候会提示小彩蛋你的reademe会展示在你的首页&#xff0c;或许你在这个readme里面的修改都会在你的主页上看到了&#x1f44…

excel导入 Easy Excel

依旧是框架感觉有东西&#xff0c;但是确实是模拟不出来&#xff0c;各种零零散散的件太多了 controller层 ApiOperation(value "导入Excel", notes "导入Excel", httpMethod "POST", response ExcelResponseDTO.class)ApiImplicitParams({…

python+requests接口自动化完整项目设计源码

前言 有很多小伙伴吵着要完整的项目源码&#xff0c;完整的项目属于公司内部的代码&#xff0c;这个是没法分享的&#xff0c;违反职业道德了&#xff0c;就算别人分享了&#xff0c;也只适用于本公司内部的业务。 所以用例的代码还是得自己去一个个写&#xff0c;我只能分享…

【Android】使用XML资源文件存储配置项:降低代码耦合性并提高可重用性

前言 在Android开发中&#xff0c;我们经常需要存储一些配置项。 例如在创建Retrofit实例时&#xff0c;需要指定baseUrl。如果需要修改替换整个项目中的baseUrl&#xff0c;那将会是一件很痛苦的事情。 为了方便管理和维护这些配置项&#xff0c;我们可以使用资源文件来存储…