什么是SPI
Service Provider Interface(接口即服务)。, JDK 内置的一种服务提供发现机制。简单说,SPI 是一种动态替换发现机制,使用 Spi 机制的优势是实现解耦,使第三方服务模块的装配控制逻辑与调用者的业务代码分离。
抛弃 Class.forName()
在 JDBC 4.0 之后实际上我们不需要再调用 Class.forName 来加载驱动程序了,我们只需要把驱动的 jar 包放到工程的类加载路径里,那么驱动就会被自动加载。
这个自动加载采用的技术叫做 SPI,数据库驱动厂商也都做了更新。可以看一下 jar 包里面的 META-INF/services 目录,里面有一个 java.sql.Driver 的文件,文件里面包含了驱动的全路径名。
SPI思想两个概念
扩展点
通过 SPI 机制查找并加载实现的接口(又称 “扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
扩展点实现
JDK SPI 是如何实现的?
提供服务接口与服务实现,在 / META-INF/services 目录下创建一个以 “接口全限定名” 命名的文件,内容为服务接口实现类的“全限定名”。全限定名:全包名. 类名
然后通过 ServiceLoader.load(服务接口类); 会从 / META-INF/services 目录下查找该接口全限定名的文件,返回所有该服务的实现集合,可循环调用服务的方法。
ServiceLoader.load(xxx.class) 加载的接口未配置也不会抛错。
/META-INF/services 目录下 文件的内容
com.jx.test.spi.Test1ServiceImpl
JDK SPI 缺点
• 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
• 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
• 扩展如果依赖其他的扩展,做不到自动注入和装配 • 不提供类似于 Spring 的 AOP 功能 • 扩展很难和其他的框架集成,比如扩展里面依赖了一个 Spring bean,原生的 Java SPI 不支持 所以 Java SPI 应付一些简单的场景是可以的,但对于 Dubbo,它的功能还是比较弱的。Dubbo 对原生 SPI 机制进行了一些扩展。接下来,我们就更深入地了解下 Dubbo 的 SPI 机制。
代码
案例:ZJJJavaBasic_2020/02/06 8:48:56_mo03s |
---|
概述
参考:
https://blog.csdn.net/u013679744/article/details/80009878#!/xh
SPI全称S而vice Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
系统设计的各个抽象,一般有很多不同的实现方案,比如通过不同类型的配置文件加载配置信息,通过不同的序列化方案实现序列化。一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔的原则。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
SPI的核心思想就是解耦,简单说就是加载配置文件(META-INF 的services),而配置文件配置了这个接口有关的很多实现类的权限定类名.
META-INF 的services是JDK源码写死的
使用场景
调用者根据实际需要替换框架的实现策略。
比如常见的例子:
数据库驱动加载类接口实现类的加载,JDBC加载不同类型数据库的驱动
日志实现类加载
dubbo中也大量使用SPI的方式实现框架的扩展,不过它对java提供的SPI进行了封装
优缺点
优点
这个设计的目的是,我的核心代码方法永远都不需要修改了,如果我要对这个代码进行扩展的话, 我就只需要在META-INF 的services 里面的配置文件写实现类全限定类名就可以了
这样的设计同时也对业务代码进行了解耦,假如说我增加类,就不需要写具体代码, 只需要在META-INF 的services的配置文件里面写类的权限定类名就行了
缺点
1.粒度不够细,我通过这个ServiceLoader.load 读取接口拿到了一堆没有必要的实现类.
解决方案
通过配置的方式(程序员自己去定义)要唯一确定一个类(策略模式),这才是细粒度方式.
2.无法按需加载。虽然 ServiceLoader 做了延迟载入,使用了LazyIterator,但是基本只能通过遍历全部获取,接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,假如我只需要其中一个,其它的并不需要这就形成了一定的资源消耗浪费
3.不具有IOC的功能,假如我有一个实现类,如何将它注入到我的容器中呢,类之间依赖关系如何完成呢?
4.serviceLoader不是线程安全的,会出现线程安全的问题
源码
https://www.yuque.com/docs/share/ff5d9788-1c38-4790-a606-c787939310bc?#