Java SPI 一 之SPI(Service Provider Interface)进阶 AutoService
一、SPI(Service Provider Interface)
1.1 介绍
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制(为某个接口寻找服务实现的机制),可以用来启用框架扩展和替换组件,其核心思想就是 解耦。
模块之间基于接口编程,模块之间不对实现类进行硬编码,当代码里涉及具体的实现类,就违反了可拔插的原则,为了实现在模块装配的时候能不在程序里动态指明,就需要spi了。
这里我们要跟API区分开来,简单介绍一下API
API(Application Programming Interface)是一种应用程序编程接口,它定义了一组用于与特定软件组件或服务进行交互的函数、方法和数据结构。
其目的主要用于提供一种与特定软件组件或服务进行交互的抽象层。
比如我们常见的系统API,接入的各种三方API,这些API的特点是实现方式以及做好了,开发者调用这些API来做一些有预期的事情。
1.2 使用场景
举个简单的例子,例如芯片公司定义了一个规范,需要第三方厂商去实现,那么对于芯片公司方来说,只需要集成对应厂商的插件,就可以完成对应规范的实现机制。 形成一种插拔式的扩展手段。
如JDBC、日志框架等都用到。
Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要(通常由服务提供者(如库或框架的开发者)实现,以提供特定功能的多种实现),我们看个图

1.3 原理
将接口的实现类放在配置文件中,在程序运行过程中读取配置文件,通过反射加载实现类。
具体流程:
–> 读取META-INF/services/下的配置文件
–> 获得所有能被实例化的类的名称
–> 通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化
–> 把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型),然后返回实例对象。
JDK中查找服务的实现的工具类是:java.util.ServiceLoader。JDK标准的SPI会一次性加载实例化扩展点的所有实现。
1.4 使用demo
我们举例,先看下完整的项目目录


我们现在需要进行股票交易,有多个券商可用。
1、先定义好接口,新建module-spi,
package com.test;public interface ITrade {void trade();
}
2、两个接口的实现
新建module-effecta module-effectb,表示不同的实现方。
这两个module分别要引用 接口的module
<dependencies><dependency><groupId>org.example</groupId><artifactId>module-spi</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency></dependencies>
添加实现
package com.effectA;import com.test.ITrade;public class TradeEffectA implements ITrade {@Overridepublic void trade() {System.out.println("券商 A");}
}
最后进行注册
java目录下 增加 resources/META-INF/services/ 目录,在该目录下创建文件 ,如下图所示:

module-effectb重复操作
再写一个测试方法,新建一个module-main,充当调用者,首先添加引用
<dependencies><dependency><groupId>org.example</groupId><artifactId>module-spi</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency><dependency><groupId>org.example</groupId><artifactId>module-effectA</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency><dependency><groupId>org.example</groupId><artifactId>module-effectB</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency></dependencies>
package com.test;import java.util.ServiceLoader;public class Test {public static void main(String[] args) {ServiceLoader<ITrade> loader = ServiceLoader.load(ITrade.class);for (ITrade itrade: loader) {itrade.trade();}}
}
那为什么配置文件为什么要放在META-INF/services下面?
可以打开ServiceLoader的代码,里面定义了文件的PREFIX如下:
private static final String PREFIX = “META-INF/services/”
我们看下源码
public final class ServiceLoader<S> implements Iterable<S>{//路径前缀(就是我们放置配置文件的目录)
private static final String PREFIX = "META-INF/services/";// 代表被加载的类或者接口private final Class<S> service;// 用于定位,加载和实例化providers的类加载器private final ClassLoader loader;// 创建ServiceLoader时采用的访问控制上下文private final AccessControlContext acc;// 缓存providers,按实例化的顺序排列private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 懒查找迭代器private LazyIterator lookupIterator;......
}
在这个例子中,每次都要手动去新建META-INF/services/的文件,是不是很麻烦,我们可以用Autoservice来简化代码,先上demo

新建module-effectc-autoservice,表示不同使用autoservice自动写入配置的的实现方。
引用 接口的module-spi 及 autoservice
<dependencies><dependency><groupId>org.example</groupId><artifactId>module-spi</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency><dependency><groupId>com.google.auto.service</groupId><artifactId>auto-service</artifactId><version>1.0-rc4</version><scope>compile</scope></dependency></dependencies>
添加实现
@AutoService(ITrade.class)
@SupportedAnnotationTypes({"com.test.ITrade"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class TredeEffectC implements ITrade {@Overridepublic void trade() {System.out.println("券商 C");}
}
到这里就结束了,是不是要简化了很多。这个机制同样适用Android,如安卓组件化,demo比较简单,就不贴代码了。

二、Autoservice
AutoService是Google开发一个自动生成SPI清单文件的框架。
自动往 resources/META-INF/services/ 写入文件。
https://github.com/google/auto
不用它也行,如果不使用它就需要手动去创建这个文件、手动往这个文件里添加服务(接口实现)。
其原理步骤:
- 遍历找到所有带有AutoService注解的类
- 验证AutoService注解的值是否正确
- 遍历所有的下沉接口
- 在META-INF/services/路径下创建文件,文件名以类的接口类全路径命名
- 在文件里写入内容,实现类(当前注解类)的全路径
我们看下autoservice的注解处理
private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {//获取所有加了AutoService注解的类Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);for (Element e : elements) {//将Element转成TypeElementTypeElement providerImplementer = MoreElements.asType(e);//获取AutoServce注解指定的valueAnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();//获取value集合Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);//如果没有指定value,报错if (providerInterfaces.isEmpty()) {error(MISSING_SERVICES_ERROR, e, annotationMirror);continue;}//遍历所有的value,获取value的完整类名(例如javax.annotation.processing.Processor)for (DeclaredType providerInterface : providerInterfaces) {TypeElement providerType = MoreTypes.asTypeElement(providerInterface);//判断是否是继承关系,是则放入providers缓存起来,否则报错if (checkImplementer(providerImplementer, providerType, annotationMirror)) {providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));} else {//报错代码,略}}}}
注解处理完毕,就会生成SPI注册文件。如果SPI路径上文件已经存在,先要把已存在的SPI清单读进内存,再把新的provider加进去,然后全部写出,覆盖原来的文件。这部分逻辑如下:
private void generateConfigFiles() {//获取文件工具类,processingEnv是AbstractProcessor的成员变量,直接拿来用。Filer filer = processingEnv.getFiler();//遍历之前解析的providers缓存for (String providerInterface : providers.keySet()) {//providerInterface就是value字段指定的接口,例如javax.annotation.processing.ProcessorString resourceFile = "META-INF/services/" + providerInterface;log("Working on resource file: " + resourceFile);try {SortedSet<String> allServices = Sets.newTreeSet();try {//已经存在的SPI文件FileObject existingFile =filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);//SPI文件中的service条目清单Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());log("Existing service entries: " + oldServices);allServices.addAll(oldServices);} catch (IOException e) {log("Resource file did not already exist.");}//新的service条目清单Set<String> newServices = new HashSet<>(providers.get(providerInterface));//如果已经存在,则不处理if (!allServices.addAll(newServices)) {log("No new service entries being added.");continue;}//以下是将缓存的services写入文件中。log("New service file contents: " + allServices);FileObject fileObject =filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);try (OutputStream out = fileObject.openOutputStream()) {ServicesFiles.writeServiceFile(allServices, out);}log("Wrote to: " + fileObject.toUri());} catch (IOException e) {fatalError("Unable to create " + resourceFile + ", " + e);return;}}}
所以我们将AutoService加到java项目中,其实就是引入了AutoServiceProcessor这个注解处理器,帮助我们处理@AutoService注解,将我们的服务(一般是APT类,也可以是其它的类,通过value指定)自动注册进SPI文件中。
三、Javapoet
javapoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件 这个框架功能非常实用。
相关文章:
Java SPI 一 之SPI(Service Provider Interface)进阶 AutoService
一、SPI(Service Provider Interface) 1.1 介绍 SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制(为某个接口寻找服务实现的机制),可以用来启用框架扩展和替换组件,其…...
C++ list类成员函数介绍
目录 🤔list模板介绍: 🤔特点: 🤔list内存结构图解: 🤔 list的成员函数: 😊list构造函数: 🔍代码示例: 🔍运行结果&…...
【服务器】本地搭建PHP简单Imagewheel私人云图床
文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道(云端设置)3.3.Cpolar稳定隧道(本地设置) 4.公网访问测…...
第四十二回:DateRangePickerDialog Widget
文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了DatePickerDialog Widget相关的内容,本章回中将介绍 DateRangePickerDialog Widget.闲话休提,让我们一起Talk Flutter吧。 概念介绍 我们在这里说的DateRangePickerDialog是一种弹出窗口,只不…...
【C++系列P3】‘类与对象‘-三部曲——[基础知识](1/3)
前言 大家好吖,欢迎来到 YY 滴 C系列 ,热烈欢迎! 【 类与对象-三部曲】的大纲主要内容如下: 如标题所示,本章是【 类与对象-三部曲】三章中的第一章节——基础知识章节,主要内容如下: 目录 一.…...
Android UEvent事件分析之Kernel上报电量
kernel-4.4\drivers\power\power_supply_core.c 当电量信息需要更新的时候,kernel会调用power_supply_changed_work这个工作队列,使用kobject_uevent函数往上发送uevent事件,action是KOBJ_CHANGE; static void power_supply_changed_work(struct work_struct *work) {uns…...
C++ vector模板和deque的简单应用
目录 🤔vector模板和deque的简单介绍: 🤔vector和deque的主要不同之处: 🤔今天我们用vector模板和deque模板实现以下简单的功能: 代码实现: 🤔讲解: 🤔vector模板和d…...
声明式事务控制
声明式事务控制 编程式事务控制相关对象 PlatformTransactionManager PlatformTransactionManager接口是spring的事务管理器,它里面提供了常用的操作事务的方法 方法说明TransactionStatus getTransaction(TransactionDefaultion defination)获取事务的状态信息…...
cisp pte模拟题
1.信息搜集 本题共三个key 端口 1433 27689 存活ip 192.168.85.137 2.访问网站27689进行信息搜集 一个登录框,sql注入失败,暴力破解失败 扫描目录 发现三个文件robots.txt ,web.config 除了robots.txt,其他都访问不了 访问robots.txt,发现一个file参数…...
Docker容器 和 Kubernetes容器集群管理系统
一、快速了解Docker 1. 什么是Docker的定义 Docker 是一个开源的应用容器引擎,基于Go语言并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以…...
港联证券|资金疯狂涌入AI,这一板块涨幅超90%!万亿巨头继续狂飙
国内外资金继续加码AI概念股。 当前,国内政策层面对于通用人工智能和算力中心的创新发展扶持政策不断推出,资本市场对于AI(人工智能)热情不断升温。在AI下游应用中,游戏板块成为最被看好的投资标的,资金流入…...
短视频矩阵系统源码-开源开发php语言搭建
短视频矩阵系统源码---------- php源码是什么? PHP源码指的就是PHP源代码,源代码是用特定编程语言编写的人类可读文本,源代码的目标是为可以转换为机器语言的计算机设置准确的规则和规范。因此,源代码是程序和网站的基础。 PHP…...
PFEA112-20 3BSE050091R20 张力控制器
您的张力测量系统包括: 张力电子PFEA111或PFEA112–PFEA111是一款经济高效的紧凑型用户友好型张力电子产品,提供 来自两个称重传感器的精确可靠的快速模拟SUM信号,用于控制 和/或监测。显示器可以显示SUM、单个A&B和差异信号小尺寸和DIN导…...
Java springboot+vue生成报纸排版页面的新闻官网
实现方案,可以作为您开始开发的参考: 后端: 使用Spring Boot框架构建Java Web应用;使用MyBatis Plus进行ORM映射,管理MySQL数据库;按照需求定义新闻数据表、类别数据表和用户数据表,使用默认的…...
Terra-Luna归零一年后:信任重建、加密未来路在何方?
本月既是Terra-Luna归零的一周年,也是FTX崩溃的第六个月,而这两个事件分别代表着2022年加密市场连环爆的开始与高潮,引发了加密行业15年历史上最可怕的生存危机。 尽管今年市场行情有所回暖,比特币开年至今涨幅70%,以太…...
Android 12.0 手动安装Persistent app失败的解决方案
1.概述 在12.0的系统产品开发中,对于一些安装app的失败问题,需要看日志 和抛出异常来判断问题所在,在最近的一些app安装失败抛出了关于Presistent app安装失败的问题,就需要从PMS安装的过程中看异常抛出的原因解决问题所在 2.手动安装Persistent app失败的解决方案的核心类…...
Unity3D安装:从命令行安装 Unity
推荐:将 NSDT场景编辑器 加入你的3D工具链 3D工具集: NSDT简石数字孪生 从命令行安装 Unity 如果要在组织中自动部署 Unity,可以从命令行安装 Editor 和其他组件。这些组件是普通的安装程序可执行程序和软件包,可以给用来自动部署…...
C++模板(详解)
非类型模板参数 模板参数可分为类型形参和非类型形参。类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。非类型形参: 用一个常量作为类(函数)模板的一个参数,在类ÿ…...
WuThreat身份安全云-TVD每日漏洞情报-2023-05-25
漏洞名称:Mitsubishi Electric MELSEC iQ-F 数据包缓冲区溢出 漏洞级别:严重 漏洞编号:CVE-2023-1424 相关涉及:Mitsubishi Electric Corporation MELSEC iQ-F Series CPU 漏洞状态:未定义 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2023-12805 漏洞名称:Ap…...
android 12.0去掉recovery模式UI页面的选项
1.概述 在12.0进行定制化开发,会根据需要去掉recovery模式的一些选项 就是在device.cpp去掉一些选项就可以了 2.去掉recovery模式UI页面的选项核心代码 bootable/recovery/recovery_ui/device.cpp bootable/recovery/recovery_main.cpp 3.去掉recovery模式UI页面的选项的核…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...

