Java中经常被提到的SPI到底是什么?

Java 程序员在日常工作中经常会听到 SPI,而且很多框架都使用了 SPI 的技术,那么问题来了,到底什么是 SPI 呢?今天阿粉就带大家好好了解一下 SPI。

SPI 概念

SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建。

这里提到了接口和实现类,那么 SPI 技术上具体有哪些技术细节呢?

  1. 接口:需要有一个功能接口;
  2. 实现类:接口只是规范,具体的执行需要有实现类才行,所以不可缺少的需要有实现类;
  3. 配置文件:要实现 SPI 机制,必须有一个与接口同名的文件存放于类路径下面的 META-INF/services 文件夹中,并且文件中的每一行的内容都是一个实现类的全路径;
  4. 类加载器 ServiceLoaderJDK 内置的一个类加载器,用于加载配置文件中的实现类;

举个栗子

上面说了 SPI 的几个概念,接下来阿粉就通过一个栗子来带大家感受一下具体的用法。

第一步

*[En]*

**

package com.example.demo.spi;/** * * Function: * Author:@author ziyou * Date:2022-10-08 21:31 * Desc:无 */public interface Compresser {  byte[] compress(byte[] bytes);  byte[] decompress(byte[] bytes);}

第二步

再写两个对应的实现类,分别是 GzipCompresser.javaWinRarCompresser.java 代码如下

package com.example.demo.spi.impl;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;

/**
 *
 * Function:
 * Author:@author ziyou
 * Date:2022-10-08 21:33
 * Desc:无
 */
public class GzipCompresser implements Compresser {
  @Override
  public byte[] compress(byte[] bytes) {
    return"compress by Gzip".getBytes(StandardCharsets.UTF_8);
  }
  @Override
  public byte[] decompress(byte[] bytes) {
    return "decompress by Gzip".getBytes(StandardCharsets.UTF_8);
  }
}
package com.example.demo.spi.impl;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;

/**
 *
 * Function:
 * Author:@author ziyou
 * Date:2022-10-08 21:33
 * Desc:无
 */
public class WinRarCompresser implements Compresser {
  @Override
  public byte[] compress(byte[] bytes) {
    return "compress by WinRar".getBytes(StandardCharsets.UTF_8);
  }

  @Override
  public byte[] decompress(byte[] bytes) {
    return "decompress by WinRar".getBytes(StandardCharsets.UTF_8);
  }
}

第三步

创建配置文件,我们接着在 resources 目录下创建一个名为 META-INF/services 的文件夹,在其中创建一个名为 com.example.demo.spi.Compresser 的文件,其中的内容如下:

com.example.demo.spi.impl.WinRarCompresser
com.example.demo.spi.impl.GzipCompresser
*[En]*

**

Java中经常被提到的SPI到底是什么?

第四步

有了上面的接口,实现类和配置文件,接下来我们就可以使用 ServiceLoader 动态加载实现类,来实现 SPI 技术了,如下所示:

package com.example.demo;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;
import java.util.ServiceLoader;

public class TestSPI {
  public static void main(String[] args) {
    ServiceLoader compressers = ServiceLoader.load(Compresser.class);
    for (Compresser compresser : compressers) {
      System.out.println(compresser.getClass());
    }
  }
}

运行的结果如下

Java中经常被提到的SPI到底是什么?
*[En]*

**

原理

知道了如何使用 SPI 接下来我们来研究一下是如何实现的,通过上面的测试我们可以看到,核心的逻辑是 ServiceLoader.load() 方法,这个方法有点类似于 Spring 中的根据接口获取所有实现类一样。

点开 ServiceLoader 我们可以看到有一个常量 PREFIX,如下所示,这也是为什么我们必须在这个路径下面创建配置文件,因为 JDK 代码里面会从这个路径里面去读取我们的文件。

Java中经常被提到的SPI到底是什么?

同时又因为在读取文件的时候使用了 class 的路径名称,因为我们使用 load 方法的时候只会传递一个 class,所以我们的文件名也必须是接口的全路径。

Java中经常被提到的SPI到底是什么?

通过 load 方法我们可以看到底层构造了一个 java.util.ServiceLoader.LazyIterator 迭代器。

Java中经常被提到的SPI到底是什么?

在迭代器中的 parse 方法中,就获取了配置文件中的实现类名称集合,然后在通过反射创建出具体的实现类对象存放到 LinkedHashMap<string,s> providers = new LinkedHashMap<>();</string,s> 中。

Java中经常被提到的SPI到底是什么?

常用的框架

SPI 技术的使用非常广泛,比如在 Dubble,不过 Dubble 中的 SPI 有经过改造的,还有我们很常见的数据库的驱动中也使用了 SPI,感兴趣的小伙伴可以去翻翻看,还有 SLF4J 用来加载不同提供商的日志实现类以及 Spring 框架等。

优缺点

前面介绍了 SPI 的原理和使用,那 SPI 有什么优缺点呢?

优点

优点当然是解耦,服务方只要定义好接口规范就好了,具体的实现可以由不同的 Jar 进行实现,只要按照规范实现功能就可以被直接拿来使用,在某些场合会被进行热插拔使用,实现了解耦的功能。

缺点

*[En]*

**

总结

阿粉今天给大家介绍了一个 SPI 的原理和实现,感兴趣的小伙伴可以自己去尝试一下,多动手有利于加深记忆哦,如果觉得我们的文章有帮助,欢迎点赞评论分享转发,让更多的人看到。

Java中经常被提到的SPI到底是什么?
更多优质内容欢迎关注公众号【Java 极客技术】,我准备了一份面试资料,回复【bbbb07】免费领取。希望能在这寒冷的日子里,帮助到大家。

Original: https://www.cnblogs.com/zi-you/p/16942914.html
Author: zi-you
Title: Java中经常被提到的SPI到底是什么?



相关阅读

Title: SSA:终于知道编译器偷摸做了哪些事

你好,我是轩脉刃。

在golang中,我们可以使用 go tool compile -S main.go 工具将一个go程序直接转换为汇编代码。但是你会发现,最终编译出来的汇编代码其实是已经被优化过了的,编译器其实很聪明,甚至将一些函数合并,取消等。至于这个过程,并不是一蹴而就的,在golang代码和最终的汇编代码中,还有一种中间的代码结构,这个结构就叫做SSA (Static Single Assignment) 静态单赋值。

这个中间的代码结构是有必要存在的,go源码解析后是一个AST树,是一个树形结构,而最终的汇编是一条一条的线性命令。将树形结构转化拆分优化为汇编命令是比较复杂的。所以这里将这么一个大的步骤分成两步走,能大大降低编译器优化的难度。

SSA:终于知道编译器偷摸做了哪些事

怎么生成ssa

我们可以使用命令 GOSSAFUNC=Foo go build index.go 来看我们将一个go源码,怎么转化为SSA的全过程的。

go代码

package array

func Foo () int {
    a := [3]int{1,3,5}
    i := 2
    elem := a[i]
    return elem
}

SSA:终于知道编译器偷摸做了哪些事

生成ssa.html

怎么看ssa

SSA:终于知道编译器偷摸做了哪些事

这个html中的ssa中间语言的语法是由 cmd/compile/internal/ssa/gen/genericOps.go 生成的。

SSA:终于知道编译器偷摸做了哪些事

每一行和对应的SSA代码都标记出来了,有一些即使没有SSA的经验,也是能立马看懂的。比如像v10 是常量1,而v13是代表指针指向a[0], v14 代表将常量1存储进入a[0]。不过有一些则不是那么容易看出了。

通过中间可以看出过了很多优化步骤才最终生成了汇编码。

SSA:终于知道编译器偷摸做了哪些事

有哪些步骤可以参考这里:https://github.com/golang/go/blob/release-branch.go1.15/src/cmd/compile/internal/ssa/compile.go#L418

至于每个步骤做了什么事情,这个就很复杂了。

关于ssa

关于ssa,我自己的理解就是,将源码的AST树,先演变成像

v1= xxx
v2= xxx
v3= xxx

这种线性执行语句。这种语句的特点就是每一行都定义了一个变量。所以叫”静态单赋值语句”。然后使用各种之间的赋值规则,可以很容易看出哪些赋值变量其实是没有用到的。对于没有用到的直接可以删除。当然还有其他各种规则,最终将v1…vn的赋值变量进行预计算,优化,最后优化为最简的几个赋值变量。这点可以从ssa.html的start到最后的trim就看出了。

最开始的源码

SSA:终于知道编译器偷摸做了哪些事

切换为AST树

SSA:终于知道编译器偷摸做了哪些事

再变成SSA语言

SSA:终于知道编译器偷摸做了哪些事

经过不断优化,变成三个执行语言。(其实这个foo函数直接可以在编译阶段将5返回)

SSA:终于知道编译器偷摸做了哪些事

最后再变化为汇编码:

SSA:终于知道编译器偷摸做了哪些事

这个编译器优化的过程,我感觉对于语言使用者还是主要适用于纯研究。

[En]

I feel that this process of compiler optimization is mainly suitable for pure research for language users.

比如想研究下数组是在栈上分配内存还是在静态数据区分配内存,可以生成ssa看看。

或者想研究下哪行代码对应哪个内部函数等。

参考:

https://gocompiler.shizhz.me/10.-golang-bian-yi-qi-han-shu-bian-yi-ji-dao-chu/10.2.1-ssa

https://oftime.net/2021/02/14/ssa/

https://draveness.me/golang/docs/part1-prerequisite/ch02-compile/golang-ir-ssa/

https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/README.md

https://en.wikipedia.org/wiki/Static_single_assignment_form

Original: https://www.cnblogs.com/yjf512/p/15719493.html
Author: 轩脉刃
Title: SSA:终于知道编译器偷摸做了哪些事

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/222787/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总