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 {@Overridepublic 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 {@Overridepublic void connectInternet() {System.out.println("connect internet By [China Mobile]");}
}
package cn.mobile;import spi.InternetService;public class BeijingChinaMobileMobile implements InternetService {@Overridepublic 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 {@Overridepublic 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;// 保存实现类的urlEnumeration<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);elseconfigs = 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数据库连接驱动,实现数据库相关的操作。

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

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

相关文章

Java ElasticSearch面试题

Java ElasticSearch面试题 前言1、ElasticSearch是什么&#xff1f;2. 说说你们公司ES的集群架构&#xff0c;索引数据大小&#xff0c;分片有多少 &#xff1f;3. ES的倒排索引是什么&#xff1f;4. ES是如何实现 master 选举的?5. 描述一下 ES索引文档的过程&#xff1a;6、…

【Emgu CV教程】7.8、图像锐化(增强)之同态滤波

文章目录 一、同态滤波大体原理二、代码三、效果举例 一、同态滤波大体原理 之前介绍的几个锐化、增强方法&#xff0c;包括更早之前介绍的图像模糊方法&#xff0c;都是基于空间域进行处理&#xff0c;也就是直接对目标点周边像素值进行各种数学运算。而这篇文章提到的同态滤…

喜迎乔迁,开启新章 ▏易我科技新办公区乔迁庆典隆重举行

2024年1月18日&#xff0c;易我科技新办公区乔迁庆典在热烈而喜庆的氛围中隆重举行。新办公区的投入使用&#xff0c;标志着易我科技将以崭新姿态迈向新的发展阶段。 ▲ 易我科技新办公区 随着公司业务的不断发展和壮大&#xff0c;为了更好地适应公司发展的需要&#xff0c;…

2024-02-29(Flink)

1.Flink原理&#xff08;角色分工&#xff09; 2.Flink执行流程 on yarn版&#xff1a; 3.相关概念 1&#xff09;DataFlow&#xff1a;Flink程序在执行的时候会被映射成一个数据流模型&#xff1b; 2&#xff09;Operator&#xff1a;数据流模型中的每一个操作被称作Operat…

npm使用国内淘宝镜像的方法整理

命令配置安装&#xff1a; 淘宝镜像&#xff1a; npm config set registry https://registry.npm.taobao.org/ 官方镜像&#xff1a; npm config set registry https://registry.npmjs.org 通过cnpm安装&#xff1a; npm install -g cnpm --registryhttps://registry.npm.…

PTA L2-003 月饼 (附坑点说明)

月饼是中国人在中秋佳节时吃的一种传统食品&#xff0c;不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量&#xff0c;请你计算可以获得的最大收益是多少。 注意&#xff1a;销售时允许取出一部分库存。样例给出的情形是这样的&#…

【Python】PyGameUI控件

哈里前段时间写了一个windows平板上自娱自乐&#xff08;春节和家人一起玩&#xff09;基于pygame的大富翁游戏。 pygame没有按钮之类的UI控件&#xff0c;写起来不怎么顺手。就自己写一个简单的框架。 仓库地址 哈里PygameUi: pygame ui封装自用 (gitee.com) 使用示例 示…

上海亚商投顾:沪指终结月线6连阴 北向资金净买入超160亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日低开高走&#xff0c;沪指重新站上3000点&#xff0c;深成指、创业板指大涨超3%。半导体产业链全…

探索那些能唤起情感共鸣的壁纸

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸、电子书搜索等功能&#xff0c;喜欢的可以赶紧去试试&#xff01;

Python:关于数据服务中的Web API的设计

搭建类似joinquant、tushare类似的私有数据服务应用&#xff0c;有以下一些点需要注意&#xff1a; 需要说明的是&#xff0c;这里讨论的是web api前后端&#xff0c;当然还有其它方案&#xff0c;thrift&#xff0c;grpc等。因为要考虑到一鱼两吃&#xff0c;本文只探讨web ap…

高项软考电子版论文答题纸(附下载)

24年软考又要来了&#xff0c;作为高项软考的拦路虎&#xff0c;论文你准备好了吗&#xff1f;&#xff01;记住在开始考试之前一定要用论文答题纸上把准备好的论文&#xff0c;在规定的时间内写上几遍&#xff0c;一是现在很少动笔写字了。二是、熟悉一下论文考试的感觉。 准备…

索引使用规则4——覆盖索引回表查询

覆盖索引&#xff1a;查询使用了索引&#xff0c;并且需要返回的列&#xff0c;在索引里面都可以找到&#xff0c;减少select*的使用 1、using index condition Extra 为using index condition 表明查找使用了索引&#xff0c;但是需要回表查询&#xff08;也就是先二级索引&…

第十八届全国大学生智能汽车竞赛——摄像头算法(附带个人经验)

文章目录 前言一、摄像头图像处理1、摄像头图像采集2、图像二值化与大津算法 二、左右边界&#xff0c;中线扫描 前言 参加了第十六&#xff0c;十七和第十八届全国大学生智能车竞赛&#xff0c;对摄像头的学习有部分心得&#xff0c;分享给大家&#xff0c;三届车赛&#xff…

【C语言基础】:深入理解指针(一)

文章目录 一、内存和地址1. 内存2. 如何理解编址 二、指针变量和地址2.1 取地址操作符(&)2.2 指针变量和解引用操作符(*)2.2.1 指针变量2.2.2 如何拆解指针变量2.2.3 解引用操作符 2.3 指针变量的大小 三、指针变量类型的意义3.1 指针的解引用3.2 指针 - 整数3.3 void*指针…

HCIA-HarmonyOS设备开发认证V2.0-习题

目录 习题一习题二&#xff08;待续...&#xff09;坚持就有收获 习题一 # HarmonyOS简介 1. 以下哪几项属于OpenHarmony的技术特性&#xff1f;&#xff08;&#xff09;A. 统一OS&#xff0c;弹性部署B. 一次开发&#xff0c;多端部署C. 硬件互助&#xff0c;资源共享2. Ope…

C# WPF编程-创建项目

1.创建新项目 选择“WPF应用程序”》“下一步” 2. 设置项目 设置项目名称&#xff0c;保存位置等参数>下一步 3.选择框架 4.项目创建成功 5.运行项目

两张二值化图像融合

python实现&#xff0c;已知两张二值化图像&#xff0c;对比两张图&#xff0c;将像素点一致的坐标保留原来颜色&#xff0c;不一致的坐标像素值变为128 读取原图 import cv2 import matplotlib.pyplot as plt import numpy as npcup_file_pathname"/home/wzc/zlt_self/…

Opencv实战(5)平滑处理与常见函数

平滑处理 Opencv实战&#xff1a; Opencv(1)读取与图像操作 Opencv(2)绘图与图像操作 Opencv(3)详解霍夫变换 Opencv(4)详解轮廓 文章目录 平滑处理1.均值滤波2.方框滤波3.高斯滤波4.中值滤波5.双边滤波 常见函数(1).createTrackbar()(2).SetMouseCallback() 图像的平滑处理是…

细数Android开发者的艰辛历程,android零基础

首先我们来看一下组件化项目和传统项目的区别: 在传统的项目里 我们通常情况下会有一个commonLib的Libary模块和一个app的application模块&#xff0c;业务中的逻辑都写在app中各个功能模块放到不同的包下。这样做有以下几个主要的缺点&#xff1a; 1.无论分包做的再好&…

Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验(一)

ACM9238 高速双通道ADC模块自助服务手册AD9238 一、实验目的 本次实验通过电脑上的网络调试助手&#xff0c;将命令帧进行发送&#xff0c;然后通过ACZ7015开发板上的以太网芯片接收&#xff0c;随后将接收到的数据转换成命令&#xff0c;从而实现对ACM9238模块采样频率、数据…