当前位置: 首页 > 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页面的选项的核…...

亚马逊卖家公开信息数据提取:反爬攻防战与 Python 批量采集实战

摘要&#xff1a; 批量获取亚马逊&#xff08;Amazon&#xff09;第三方卖家的商业名称、信用代码和注册地址等信息&#xff0c;对于跨境 B2B 拓客和供应链分析具有重要意义。然而&#xff0c;亚马逊的 Cloudflare 盾和 Robot 验证码构成了极高的反爬门槛。本文将深度解析亚马逊…...

SkillVLA:通过技能复用应对双-臂操纵中的组合多样性

26年3月来自新加坡国立、北京中关村学院、上海创新研究院、上海AI实验室、上海交大和复旦的论文“SkillVLA: Tackling Combinatorial Diversity in Dual-Arm Manipulation via Skill Reuse”。 视觉-语言-动作&#xff08;VLA&#xff09;模型近期取得的进展&#xff0c;已充分…...

3步解锁专业级MMD创作:Blender插件如何重塑二次元动画工作流

3步解锁专业级MMD创作&#xff1a;Blender插件如何重塑二次元动画工作流 【免费下载链接】blender_mmd_tools MMD Tools is a blender addon for importing/exporting Models and Motions of MikuMikuDance. 项目地址: https://gitcode.com/gh_mirrors/bl/blender_mmd_tools …...

终极艾尔登法环帧率解锁指南:轻松突破60FPS限制

终极艾尔登法环帧率解锁指南&#xff1a;轻松突破60FPS限制 【免费下载链接】EldenRingFpsUnlockAndMore A small utility to remove frame rate limit, change FOV, add widescreen support and more for Elden Ring 项目地址: https://gitcode.com/gh_mirrors/el/EldenRing…...

转行网络安全运维:从0到1的可落地指南

转行网络安全运维&#xff1a;从0到1的可落地指南 一、 「3个核心技能&#xff1a;从零起步也能会」 网上学习资料多到爆炸&#xff0c;不用纠结“哪个最好”&#xff0c;记住一句话&#xff1a;**能学会、能上手的就是好的**&#xff01;不管是免费视频还是付费课&#xff0c…...

Arcmap实操:如何用‘渔网’给你的地图做一次‘CT扫描’——以韶关市路网密度可视化为例

Arcmap实操&#xff1a;如何用‘渔网’给你的地图做一次‘CT扫描’——以韶关市路网密度可视化为例 想象一下&#xff0c;医生通过CT扫描将人体内部结构分层呈现&#xff0c;而GIS中的"渔网"工具同样能对城市路网进行"切片式"分析。这种空间离散化技术&…...

【大模型聚合平台深度评测:阿里云百炼 vs 腾讯云 ADP,企业如何选型?】

大模型聚合平台深度评测&#xff1a;阿里云百炼 vs 腾讯云 ADP&#xff0c;企业如何选型&#xff1f; 随着大模型技术的快速发展&#xff0c;越来越多的企业开始将 AI 能力融入到业务流程中。然而&#xff0c;面对市场上众多的大模型产品&#xff0c;企业往往面临着 “选择困难…...

MPC Video Renderer终极指南:如何在Windows上实现专业级视频渲染体验

MPC Video Renderer终极指南&#xff1a;如何在Windows上实现专业级视频渲染体验 【免费下载链接】VideoRenderer Внешний видео-рендерер 项目地址: https://gitcode.com/gh_mirrors/vi/VideoRenderer MPC Video Renderer是一款专为Windows平台设计…...

概率论:常见分布的期望与方差、中心极限定理、切比雪夫不等式

目录 一、0、1分布 二、二项分布 三、泊松分布 四、均匀分布​ 五、指数分布 六、正态分布 七、中心极限定理及其应用 &#xff08;1&#xff09;中心极限定理的定义 &#xff08;2&#xff09;使用示例 八、切比雪夫不等式 &#xff08;1&#xff09;切比雪夫不…...

Lovable后端集成方案深度拆解(含Spring Boot 3.2+GraalVM+OpenTelemetry完整Demo)

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;Lovable后端集成方案全景概览 Lovable 是一个面向现代 Web 应用的轻量级后端协作框架&#xff0c;其核心设计理念是“可组合、可观测、可演进”。它不绑定特定语言或运行时&#xff0c;而是通过标准化协议与契…...