Java SPI:Service Provider Interface

SPI机制简介

        SPI(Service Provider Interface),是从JDK6开始引入的,一种基于ClassLoader来发现并加载服务的机制。

        一个标准的SPI,由3个组件构成,分别是:

  • Service:是一个公开的接口或者抽象类,定义了一个抽象的功能模块;
  • Service Provider:是Service接口的实现子类;
  • ServiceLoader:是SPI机制的核心组件,负责在运行时发现并加载Service Provider。

SPI运行流程

        SPI运行流程如下图所示,

ServiceLoader类

        ServiceLoader是SPI机制的核心组件,负责在运行时发现并加载Service Provider。该类提供了load方法,用于在程序运行过程中去加载第三方提供的Service接口实现类,得到接口实例;后续过程中,只需要通过接口实例去执行对应的操作即可。

        假设,我们有这样一个InternetService 接口,用来提供网络连接服务。

/**
 * 网络连接服务接口SPI-Service
 */
public interface InternetService {
    void connectInternet();
}

        然后为其提供接口实现子类,

package cn.mobile;

import spi.InternetService;

public class BeijingChinaMobileMobile implements InternetService {
    @Override
    public void connectInternet() {
        System.out.println("connect internet By [Beijing China Mobile]");
    }
}

       这样写在单体项目中自然是可以的,但是,如果我们要让别人也能在项目中使用这个接口提供的网络连接服务,就有点难受了。好在SPI机制就是用来做服务发现和加载工作的,我们可以将其改造成符合SPI标准的一套通用工具。

service服务定义

        接口就是在定义标准,而这个标准需要交由第三方进行实现。

1. 创建maven项目,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>simple_spi_example</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>simple-api</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

 2.定义接口标准,

package spi;

/**
 * 网络连接服务接口SPI-Service
 */
public interface InternetService {
    void connectInternet();
}

service provider服务的第三方实现

        service provider是Service接口的实现子类。以下我们提供两个第三方实现,分别命名为A、B。

第三方A实现

1. 创建Maven项目,引入接口标准依赖,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>simple_spi_example</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>simple-spi-mobile</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <artifactId>simple-api</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

2. 实现SPI接口,

package cn.mobile;

import spi.InternetService;

public class ChinaMobile implements InternetService {
    @Override
    public void connectInternet() {
        System.out.println("connect internet By [China Mobile]");
    }
}
package cn.mobile;

import spi.InternetService;

public class BeijingChinaMobileMobile implements InternetService {
    @Override
    public void connectInternet() {
        System.out.println("connect internet By [Beijing China Mobile]");
    }
}

3.提供service元数据,

        在resources目录下,新建资源文件META-INF\services\spi.InternetService,文件名:spi.InternetService(父接口的全路径名称),

cn.mobile.ChinaMobile
cn.mobile.BeijingChinaMobileMobile 

第三方B实现

1. 创建Maven项目,引入接口标准依赖,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>simple_spi_example</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>simple-spi-unicom</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <artifactId>simple-api</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2. 实现SPI接口,

package cn.unicom;

import spi.InternetService;

public class ChinaUniCom implements InternetService {
    @Override
    public void connectInternet() {
        System.out.println("connect internet By [ChinaUniCom]");
    }
}

3.提供service元数据,

        在resources目录下,新建资源文件META-INF\services\spi.InternetService,文件名:spi.InternetService(父接口的全路径名称),

cn.unicom.ChinaUniCom

ServiceLoader服务发现和服务加载

 1. 在主项目中引入service服务的第三方实现相关依赖,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>simple_spi_example</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>smaple-company</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <artifactId>simple-api</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>simple-spi-unicom</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>simple-spi-mobile</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

2. 通过ServiceLoader发现并加载服务

package com.company;

import spi.InternetService;

import java.util.ServiceLoader;

public class Application {
    public static void main(String[] args) {
        ServiceLoader<InternetService> load = ServiceLoader.load(InternetService.class);
        for (InternetService provider : load) {
            provider.connectInternet();
        }
    }
}

3.执行结果如下

ServiceLoader源码分析

 

        分析,

// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S>
{
    // 查找配置文件的目录
    private static final String PREFIX = "META-INF/services/";
    // 表示要被加载的服务的类或接口
    private final Class<S> service;
    // 这个ClassLoader用来定位,加载,实例化服务提供者
    private final ClassLoader loader;
    // 访问控制上下文
    private final AccessControlContext acc;
    // 缓存已经被实例化的服务提供者,按照实例化的顺序存储
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 迭代器
    private LazyIterator lookupIterator; 
}
// 服务提供者查找的迭代器
public Iterator<S> iterator() {
    return new Iterator<S>() {
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
        // hasNext方法
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
        // next方法
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
    };
}
// 服务提供者查找的迭代器
private class LazyIterator implements Iterator<S> {
    // 服务提供者接口
    Class<S> service;
    // 类加载器
    ClassLoader loader;
    // 保存实现类的url
    Enumeration<URL> configs = null;
    // 保存实现类的全名
    Iterator<String> pending = null;
    // 迭代器中下一个实现类的全名
    String nextName = null;
 
    public boolean hasNext() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    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;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }
 
    public S next() {
        if (!hasNext()) {
            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, x);
        }
        throw new Error();          // This cannot happen
    }
}

SPI应用场景举例

在JDBC4.0之前,连接数据库的时候,通常会用Class.forName("com.mysql.jdbc.Driver")这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要Class.forName来加载驱动,直接获取连接即可,这里使用了Java的SPI扩展机制来实现。

        注册数据库连接驱动就是一个典型的例子,以PostGreSQL数据库连接驱动为例,我们知道:java中定义了接口java.sql.Driver,但是并没有提供具体的实现,具体的实现都是由不同厂商来提供的,所以我们实际开发时,需要先去找到对应的数据库连接驱动,把驱动加载到应用中,然后才能去执行数据库的种种操作。

        查看postgresql依赖jar包,会发现在META-INFO下的services路径下,也提供了java.sql.Driver驱动类的实现子类信息,

        文件内容如下,

org.postgresql.Driver

         这样,就可以基于SPI机制,动态加载第三方提供的Driver数据库连接驱动,实现数据库相关的操作。

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

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

相关文章

模型优化_XGBOOST学习曲线及改进,泛化误差

代码 from xgboost import XGBRegressor as XGBR from sklearn.ensemble import RandomForestRegressor as RFR from sklearn.linear_model import LinearRegression as LR from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split,c…

javascript作用域编译浅析

作用域思维导图 1&#xff1a;编译原理 分词/词法分析 如果词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时&#xff0c;调用的是有状态的解析规则&#xff0c;那么这个过程就被称为词法分析。 解析/语法分析 由词法单元流转换成一个由元素逐级嵌套所组…

Linux 下安装Jupyter

pip3 install jupyter pip3 install ipython -------------------------------------------- pip3 install jupyterlab jupyter lab pip3 list | grep jupyterlab 启动&#xff1a; python3 -m jupyter lab 2.安装朱皮特 pip3 install -i https://pypi.douban.com/simpl…

免费音频剪辑

在数字时代&#xff0c;音频剪辑已成为许多职业和爱好者不可或缺的技能。无论是制作播客、教育视频、还是进行广告宣传&#xff0c;高质量的音频剪辑都能为作品增色不少。今天&#xff0c;我要为大家强烈安利一款免费且功能强大的音频剪辑工具&#xff0c;它绝对是你办公桌上不…

[分类指标]准确率、精确率、召回率、F1值、ROC和AUC、MCC马修相关系数

准确率、精确率、召回率、F1值 定义&#xff1a; 1、准确率&#xff08;Accuracy&#xff09; 准确率是指分类正确的样本占总样本个数的比例。准确率是针对所有样本的统计量。它被定义为&#xff1a; 准确率能够清晰的判断我们模型的表现&#xff0c;但有一个严重的缺陷&…

【OJ比赛日历】快周末了,不来一场比赛吗? #03.02-03.08 #11场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2024-03-02&#xff08;周六&#xff09; #4场比赛2024-03-03…

如何解决局域网tcp延迟高来进行安全快速内外网传输呢?

在当今企业运营中&#xff0c;数据的快速流通变得至关重要&#xff0c;但局域网内的TCP延迟问题却成为了数据传输的障碍。本文旨在分析局域网TCP延迟的成因&#xff0c;并探讨几种企业数据传输的常见模式&#xff0c;以及如何为企业选择合适的传输策略&#xff0c;以确保数据在…

软考52-上午题-【数据库】-关系模式2

一、关系模式的回顾 见&#xff1a;软考38-上午题-【数据库】-关系模式 二、关系模式 2-1、关系模式的定义 示例&#xff1a; 念法&#xff1a;A——>B A决定B&#xff0c;或者&#xff0c;B依赖于A。 2-2、函数依赖 1、非平凡的函数依赖 如果X——>Y&#xff0c;&a…

Javascript:输入输出

目录 一.前言 二.正文 1.输出 2.输入 3.字面量 概念&#xff1a; 三.结语 一.前言 Javascript作为运行浏览器的语言&#xff0c;对于学习前端的同学来说十分重要&#xff0c;那么从现在开始我们将开始介绍有关 Javascript。 二.正文 1.输出 document.write() : 向body内…

UVa11726 Crime Scene

题目链接 UVa11726 - Crime Scene 题意 给定n&#xff08;n≤100&#xff09;个物体&#xff0c;每个物体都是一个圆或者k&#xff08;k≤10&#xff09;边形&#xff0c;用长度尽量小的绳子把它们包围起来。 分析 孟加拉国Manzurur Rahman Khan (Sidky)大神出的难题&#xff…

HTML5+CSS3+JS小实例:右键菜单

实例:右键菜单 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><met…

Linux x86平台获取sys_call_table

文章目录 前言一、根据call *sys_call_table来获取二、使用dump_stack三、使用sys_close参考资料 前言 Linux 3.10.0 – x86_64 最简单获取sys_call_table符号的方法&#xff1a; # cat /proc/kallsyms | grep sys_call_table ffffffff816beee0 R sys_call_table一、根据cal…

想给金三银四找工作的程序员几点建议,被面试官问的Android问题难倒了

Android没凉&#xff0c;只是比以前难混了 多年前Android异军突起&#xff0c;成了新的万亿级市场&#xff0c;无数掘金人涌入&#xff0c;期待可以一展拳脚。 那时候大环境下的手游圈&#xff0c;只要你能有个可以运行的连连看就能找到工作&#xff0c;走上赛道被浪潮推着前…

LeetCode73题:矩阵置零(python3)

代码思路&#xff1a; 这里用矩阵的第一行和第一列来标记是否含有0的元素&#xff0c;但这样会导致原数组的第一行和第一列被修改&#xff0c;无法记录它们是否原本包含 0。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0。 class Solution:def setZe…

二、TensorFlow结构分析(2)

目录 1、会话 1.1 __init__(target,graphNone,configNone) 1.2 会话的run() 1.3 feed操作 TF数据流图图与TensorBoard会话张量变量OP高级API 1、会话 1.1 __init__(target,graphNone,configNone) def session_demo():# 会话的演示# Tensorflow实现加法运算a_t tf.constan…

python+Django+Neo4j中医药知识图谱与智能问答平台

文章目录 项目地址基础准备正式运行 项目地址 https://github.com/ZhChessOvO/ZeLanChao_KGQA 基础准备 请确保您的电脑有以下环境&#xff1a;python3&#xff0c;neo4j 在安装目录下进入cmd&#xff0c;输入指令“pip install -r requirement.txt”,安装需要的python库 打…

【二叉搜索树】【递归】【迭代】Leetcode 700. 二叉搜索树中的搜索

【二叉搜索树】【递归】【迭代】Leetcode 700. 二叉搜索树中的搜索 二叉搜索树解法1 递归法解法2 迭代法 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- 二叉搜索树 二叉搜索树&#xff08;Binary Search Tree&#xff…

【详识JAVA语言】运算符

什么是运算符 计算机的最基本的用途之一就是执行数学运算&#xff0c;比如&#xff1a; int a 10; int b 20;a b; a < b; 上述 和< 等就是运算符&#xff0c;即&#xff1a;对操作数进行操作时的符号&#xff0c;不同运算符操作的含义不同。 作为一门计算机语言&…

mprpc分布式RPC网络通信框架

mprpc 项目介绍 该项目是一个基于muduo、Protobuf和Zookeeper实现的轻量级分布式RPC网络通信框架。 可以把任何单体架构系统的本地方法调用&#xff0c;重构成基于TCP网络通信的RPC远程方法调用&#xff0c;实现同一台机器的不同进程之间的服务调用&#xff0c;或者不同机器…

FreeRTOS 软件定时器

目录 一、软件定时器简介 1、软件定时器概述 2、编写回调函数的注意事项 二、软件定时器实现机制 1、软件定时器实现机制 2、软件定时器相关配置 三、单次定时器 四、周期定时器 五、软件定时器的基本操作 1、创建软件定时器 2、复位软件定时器 3、开启软件定时器 …