Spring SPI

 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的作用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端开发学习 (一) 搭建Vue基础环境

一、环境搭建 1、安装nodejs #下载地址 https://nodejs.org/dist/v20.9.0/node-v20.9.0-x64.msi 2、配置环境变量 上面下载完安装包后自行安装&#xff0c;安装完成后安装下图操作添加环境变量 #查看版本 node --version v20.9.0# npm --version 10.1.03、配置npm加速源 np…

【Ubuntu】Windows远程Ubuntu系统

步骤 开启ssh服务并开放22端口关闭防火墙ufw或iptables &#xff1b;或者将远程端口添加到入站与出站规则安装xrdp并将xrdp用户添加到ssl-cert用户组mstsc 远程&#xff0c;输入账号密码 1、开启ssh服务 1.1. 查看ssh是否已经开启 sudo ps -e | grep ssh如果最后返回是sshd…

SQL基础理论篇(六):多表的连接方式

文章目录 简介笛卡尔积等值连接非等值连接外连接自连接其他SQL92与SQL99中连接的区别不同DBMS下使用连接的注意事项参考文献 简介 SQL92中提供了5类连接方式&#xff0c;分别是笛卡尔积、等值连接、非等值连接、外连接(左连接、右连接、全外连接(full outer join、全连接))和自…

SpringBoot整合Redis使用基于注解的缓存

环境准备 注解 EnableCaching CacheConfig CacheConfig 提供了一种在类级别共享公共缓存相关设置的机制。 | 参数 | 作用 | | | — | — | — | | cacheNames | 使用在类上的默认缓存名称 | | | keyGenerator | 用于类的默认KeyGenerator的bean名称 | | | cacheManager | 自定…

Android——模块级build.gradle配置——applicationId和namespace

官方地址&#xff1a; 配置应用模块-applicationId和namespace了解 build.gradle 中的实用设置。https://developer.android.google.cn/studio/build/configure-app-module?hlzh-cn 产生那些异常场景&#xff1a; Android&#xff1a;Namespace not specified. Please spec…

【编译原理】Chapter1概述

课程主要内容&#xff1a;程序设计语言编译程序构造的基本原理和基本实现技术 文章目录 什么是编译程序为什么要学编译原理计算思维(Computational Thinking)学习意义编译原理和方法的应用 编译过程概述词法分析语法分析中间代码生成优化目标代码产生 编译程序的结构编译程序总…

国内crm解决方案的主要提供商有哪些?对比7家

目前国内CRM服务商1410家&#xff0c;今年1-7月CRM服务商新注册19家。如何从众多服务商中挑选出合适的一家&#xff0c;无疑是一项耗时耗力的大工程。为此&#xff0c;本文将为根据国内外知名机构、媒体、网站发布、百度指数、行业知名度等维度考量&#xff0c;选择出7大CRM系统…

城市网吧视频智能监控方案,实现视频远程集中监控

网吧环境较为复杂&#xff0c;电脑设备众多且人员流动性大&#xff0c;极易发生人员或消防事故&#xff0c;亟需改变&#xff0c;TSINGSEE青犀AI智能网吧视频监管方案可以帮助实现对网吧环境和用户活动的实时监控和管理。 1、视频监控系统 在网吧内部布置高清摄像头&#xff0…

Microsoft发布了一份关于其产品安全修复的 11 月报告。

&#x1f47e; 平均每天有 50 多个漏洞被发现&#xff0c;其中一些会立即被网络犯罪分子利用。我们把那些现在很受网络犯罪分子欢迎&#xff0c;或者根据我们的预测&#xff0c;在不久的将来可能会被大量利用的漏洞称为趋势漏洞。 在攻击者开始利用这些漏洞之前 12 小时&#…

DocCMS keyword SQL注入漏洞复现 [附POC]

文章目录 DocCMS keyword SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 DocCMS keyword SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测…

蓝桥杯 map

map 代码示例 #include<iostream> #include<map> using namespace std; int main(){//创建并初始化mapmap<int,string> myMap{{1,"Apple"},{2,"Banana"},{3,"Orange"}} ;//插入元素myMap.insert(make_pair(4,"Grapes&qu…

【云原生-Kurbernetes篇】K8s的存储卷/数据卷+PV与PVC

这是一个目录标题 一、Kurbernetes中的存储卷1.1 为什么需要存储卷&#xff1f;1.2 存储卷概述1.2.1 简介1.2.2 volume字段 1.3 常用的存储卷类型1.3.1 emptyDir&#xff08;临时存储卷&#xff09;1.3.2 hostPath&#xff08;节点存储卷&#xff09;1.3.3 nfs1.3.4 cephfs 二、…

大功率电源芯片WD5030L

电源管理芯片作为现代电子设备中最关键的元件之一&#xff0c;直接影响着设备的性能和效率。而大功率电源芯片作为电源管理芯片中的一种&#xff0c;其性能和应用领域更加广泛。本文将介绍一款具有宽VIN输入范围、高效率和多种优良性能的大功率电源芯片WD5030L&#xff0c;并探…

SpringCloud-Gateway修改Response响应体,并解决大数据量返回不全等问题

官网相关案例&#xff1a; Spring Cloud Gatewayhttps://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-modifyresponsebody-gatewayfilter-factory ModifyRequestBodyGatewayFilterFactory类: https://github.com/spring-cloud/spring-cloud-gate…

五分钟k8s实战-Istio 网关

istio-03.png 在上一期 k8s-服务网格实战-配置 Mesh 中讲解了如何配置集群内的 Mesh 请求&#xff0c;Istio 同样也可以处理集群外部流量&#xff0c;也就是我们常见的网关。 其实和之前讲到的k8s入门到实战-使用Ingress Ingress 作用类似&#xff0c;都是将内部服务暴露出去的…

kafka分布式安装部署

1.集群规划 2.集群部署 官方下载地址&#xff1a;http://kafka.apache.org/downloads.html &#xff08;1&#xff09;上传并解压安装包 [zhangflink9wmwtivvjuibcd2e package]$ tar -zxvf kafka_2.12-3.3.1.tgz -C ../software/&#xff08;2&#xff09;修改解压后的文件…

qt笔记之qml和C++的交互系列(一):初记

code review! —— 杭州 2023-11-16 夜 文章目录 一.qt笔记之qml和C的交互&#xff1a;官方文档阅读理解0.《Overview - QML and C Integration》中给出五种QML与C集成的方法1.Q_PROPERTY&#xff1a;将C类的成员变量暴露给QML2.Q_INVOKABLE()或public slots&#xff1a;将C类…

网络编程TCP/UDP通信

1 网络通信概述 1.1 IP 和端口 所有的数据传输&#xff0c;都有三个要素 &#xff1a;源、目的、长度。 怎么表示源或者目的呢&#xff1f;请看图 所以&#xff0c;在网络传输中需要使用“IP 和端口”来表示源或目的。 1.2 网络传输中的 2 个对象&#xff1a;server 和 cl…

Linux操作系统 - 进程控制

目录 进程创建 进程退出 进程等待 进程替换 进程创建 在操作系统中&#xff0c;除了系统启动之后的第一个进程(根进程&#xff0c;1号进程)由系统来创建外&#xff0c;其余进程都必须由已存在的进程来创建。其中&#xff0c;这个新创建的进程叫做子进程&#xff0c;而创建…