Java
程序员在日常工作中经常会听到 SPI
,而且很多框架都使用了 SPI
的技术,那么问题来了,到底什么是 SPI
呢?今天阿粉就带大家好好了解一下 SPI。
SPI 概念
SPI
全称是 Service Provider Interface
,是一种 JDK
内置的动态加载实现扩展点的机制,通过 SPI
技术我们可以动态获取接口的实现类,不用自己来创建。
这里提到了接口和实现类,那么 SPI
技术上具体有哪些技术细节呢?
- 接口:需要有一个功能接口;
- 实现类:接口只是规范,具体的执行需要有实现类才行,所以不可缺少的需要有实现类;
- 配置文件:要实现
SPI
机制,必须有一个与接口同名的文件存放于类路径下面的META-INF/services
文件夹中,并且文件中的每一行的内容都是一个实现类的全路径; - 类加载器
ServiceLoader
:JDK
内置的一个类加载器,用于加载配置文件中的实现类;
举个栗子
上面说了 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.java
和 WinRarCompresser.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]*
**

第四步
有了上面的接口,实现类和配置文件,接下来我们就可以使用 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());
}
}
}
运行的结果如下

*[En]*
**
原理
知道了如何使用 SPI
接下来我们来研究一下是如何实现的,通过上面的测试我们可以看到,核心的逻辑是 ServiceLoader.load()
方法,这个方法有点类似于 Spring
中的根据接口获取所有实现类一样。
点开 ServiceLoader
我们可以看到有一个常量 PREFIX
,如下所示,这也是为什么我们必须在这个路径下面创建配置文件,因为 JDK
代码里面会从这个路径里面去读取我们的文件。

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

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

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

常用的框架
SPI 技术的使用非常广泛,比如在 Dubble
,不过 Dubble
中的 SPI
有经过改造的,还有我们很常见的数据库的驱动中也使用了 SPI
,感兴趣的小伙伴可以去翻翻看,还有 SLF4J
用来加载不同提供商的日志实现类以及 Spring
框架等。
优缺点
前面介绍了 SPI
的原理和使用,那 SPI
有什么优缺点呢?
优点
优点当然是解耦,服务方只要定义好接口规范就好了,具体的实现可以由不同的 Jar
进行实现,只要按照规范实现功能就可以被直接拿来使用,在某些场合会被进行热插拔使用,实现了解耦的功能。
缺点
*[En]*
**
总结
阿粉今天给大家介绍了一个 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
我们可以使用命令 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.html
怎么看ssa

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

每一行和对应的SSA代码都标记出来了,有一些即使没有SSA的经验,也是能立马看懂的。比如像v10 是常量1,而v13是代表指针指向a[0], v14 代表将常量1存储进入a[0]。不过有一些则不是那么容易看出了。
通过中间可以看出过了很多优化步骤才最终生成了汇编码。

有哪些步骤可以参考这里: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就看出了。
最开始的源码

切换为AST树

再变成SSA语言

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

最后再变化为汇编码:

这个编译器优化的过程,我感觉对于语言使用者还是主要适用于纯研究。
[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/
转载文章受原作者版权保护。转载请注明原作者出处!