当前位置: 首页 > news >正文

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

不用它也行,如果不使用它就需要手动去创建这个文件、手动往这个文件里添加服务(接口实现)。

其原理步骤:

  1. 遍历找到所有带有AutoService注解的类
  2. 验证AutoService注解的值是否正确
  3. 遍历所有的下沉接口
  4. 在META-INF/services/路径下创建文件,文件名以类的接口类全路径命名
  5. 在文件里写入内容,实现类(当前注解类)的全路径

我们看下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&#xff08;Service Provider Interface&#xff09; 1.1 介绍 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种 服务提供发现机制(为某个接口寻找服务实现的机制)&#xff0c;可以用来启用框架扩展和替换组件&#xff0c;其…...

C++ list类成员函数介绍

目录 &#x1f914;list模板介绍&#xff1a; &#x1f914;特点&#xff1a; &#x1f914;list内存结构图解&#xff1a; &#x1f914; list的成员函数&#xff1a; &#x1f60a;list构造函数&#xff1a; &#x1f50d;代码示例&#xff1a; &#x1f50d;运行结果&…...

【服务器】本地搭建PHP简单Imagewheel私人云图床

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…...

第四十二回:DateRangePickerDialog Widget

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了DatePickerDialog Widget相关的内容,本章回中将介绍 DateRangePickerDialog Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在这里说的DateRangePickerDialog是一种弹出窗口&#xff0c;只不…...

【C++系列P3】‘类与对象‘-三部曲——[基础知识](1/3)

前言 大家好吖&#xff0c;欢迎来到 YY 滴 C系列 &#xff0c;热烈欢迎&#xff01; 【 类与对象-三部曲】的大纲主要内容如下&#xff1a; 如标题所示&#xff0c;本章是【 类与对象-三部曲】三章中的第一章节——基础知识章节&#xff0c;主要内容如下&#xff1a; 目录 一.…...

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的简单应用

目录 &#x1f914;vector模板和deque的简单介绍: &#x1f914;vector和deque的主要不同之处&#xff1a; &#x1f914;今天我们用vector模板和deque模板实现以下简单的功能&#xff1a; 代码实现&#xff1a; &#x1f914;讲解&#xff1a; &#x1f914;vector模板和d…...

声明式事务控制

声明式事务控制 编程式事务控制相关对象 PlatformTransactionManager PlatformTransactionManager接口是spring的事务管理器&#xff0c;它里面提供了常用的操作事务的方法 方法说明TransactionStatus getTransaction(TransactionDefaultion defination)获取事务的状态信息…...

cisp pte模拟题

1.信息搜集 本题共三个key 端口 1433 27689 存活ip 192.168.85.137 2.访问网站27689进行信息搜集 一个登录框&#xff0c;sql注入失败&#xff0c;暴力破解失败 扫描目录 发现三个文件robots.txt ,web.config 除了robots.txt,其他都访问不了 访问robots.txt,发现一个file参数…...

Docker容器 和 Kubernetes容器集群管理系统

一、快速了解Docker 1. 什么是Docker的定义 Docker 是一个开源的应用容器引擎&#xff0c;基于Go语言并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以…...

港联证券|资金疯狂涌入AI,这一板块涨幅超90%!万亿巨头继续狂飙

国内外资金继续加码AI概念股。 当前&#xff0c;国内政策层面对于通用人工智能和算力中心的创新发展扶持政策不断推出&#xff0c;资本市场对于AI&#xff08;人工智能&#xff09;热情不断升温。在AI下游应用中&#xff0c;游戏板块成为最被看好的投资标的&#xff0c;资金流入…...

短视频矩阵系统源码-开源开发php语言搭建

短视频矩阵系统源码---------- php源码是什么&#xff1f; PHP源码指的就是PHP源代码&#xff0c;源代码是用特定编程语言编写的人类可读文本&#xff0c;源代码的目标是为可以转换为机器语言的计算机设置准确的规则和规范。因此&#xff0c;源代码是程序和网站的基础。 PHP…...

PFEA112-20 3BSE050091R20 张力控制器

您的张力测量系统包括&#xff1a; 张力电子PFEA111或PFEA112–PFEA111是一款经济高效的紧凑型用户友好型张力电子产品&#xff0c;提供 来自两个称重传感器的精确可靠的快速模拟SUM信号&#xff0c;用于控制 和/或监测。显示器可以显示SUM、单个A&B和差异信号小尺寸和DIN导…...

Java springboot+vue生成报纸排版页面的新闻官网

实现方案&#xff0c;可以作为您开始开发的参考&#xff1a; 后端&#xff1a; 使用Spring Boot框架构建Java Web应用&#xff1b;使用MyBatis Plus进行ORM映射&#xff0c;管理MySQL数据库&#xff1b;按照需求定义新闻数据表、类别数据表和用户数据表&#xff0c;使用默认的…...

Terra-Luna归零一年后:信任重建、加密未来路在何方?

本月既是Terra-Luna归零的一周年&#xff0c;也是FTX崩溃的第六个月&#xff0c;而这两个事件分别代表着2022年加密市场连环爆的开始与高潮&#xff0c;引发了加密行业15年历史上最可怕的生存危机。 尽管今年市场行情有所回暖&#xff0c;比特币开年至今涨幅70%&#xff0c;以太…...

Android 12.0 手动安装Persistent app失败的解决方案

1.概述 在12.0的系统产品开发中,对于一些安装app的失败问题,需要看日志 和抛出异常来判断问题所在,在最近的一些app安装失败抛出了关于Presistent app安装失败的问题,就需要从PMS安装的过程中看异常抛出的原因解决问题所在 2.手动安装Persistent app失败的解决方案的核心类…...

Unity3D安装:从命令行安装 Unity

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 从命令行安装 Unity 如果要在组织中自动部署 Unity&#xff0c;可以从命令行安装 Editor 和其他组件。这些组件是普通的安装程序可执行程序和软件包&#xff0c;可以给用来自动部署…...

C++模板(详解)

非类型模板参数 模板参数可分为类型形参和非类型形参。类型形参&#xff1a; 出现在模板参数列表中&#xff0c;跟在class或typename关键字之后的参数类型名称。非类型形参&#xff1a; 用一个常量作为类&#xff08;函数&#xff09;模板的一个参数&#xff0c;在类&#xff…...

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页面的选项的核…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...