作者:废物大师兄
起源:www.cnblogs.com/cjsblog/p/14346766.html
SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制。实质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样能够在运行时,动静为接口替换实现类。
在Java中SPI是被用来设计给服务提供商做插件应用的。基于策略模式来实现动静加载的机制。咱们在程序只定义一个接口,具体的实现交个不同的服务提供者;在程序启动的时候,读取配置文件,由配置确定要调用哪一个实现。有很多组件的实现,如日志、数据库拜访等都是采纳这样的形式,最罕用的就是 JDBC 驱动。
1、Java SPI
外围类:java.util.ServiceLoader
服务是一组家喻户晓的接口和(通常是形象的)类。服务提供者是服务的特定实现。提供者中的类通常实现接口,并子类化服务自身中定义的类。服务提供者能够以扩大的模式装置在Java平台的实现中,即搁置在任何常见扩大目录中的jar文件。提供程序也能够通过将它们增加到应用程序的类门路或其余特定于平台的办法来提供。
通过在资源目录META-INF/services中搁置一个提供程序配置文件来辨认服务提供程序。文件名是服务类型的齐全限定二进制名称。该文件蕴含具体提供程序类的齐全限定二进制名的列表,每行一个。每个名称四周的空格和制表符以及空白行将被疏忽。正文字符是'#';在每一行中,第一个正文字符之后的所有字符都将被疏忽。文件必须用UTF-8编码。
依照下面的办法,咱们来写个例子试一下
首先,定义一个接口Car
package org.example;public interface Car { void run();}
两个实现类
ToyotaCar.java
package org.example;public class ToyotaCar implements Car { @Override public void run() { System.out.println("Toyota"); }}
HondaCar.java
package org.example;public class HondaCar implements Car { @Override public void run() { System.out.println("Honda"); }}
在META-INF/services下创立一个名为org.example.Car的文本文件
org.example.ToyotaCarorg.example.HondaCar
最初,写个测试类运行看一下成果
package org.example;import java.util.ServiceLoader;public class App{ public static void main( String[] args ) { ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); }}
跟一下ServiceLoader的代码,看看是怎么找到服务实现的
用以后线程的类加载器加载
接口和类加载器都有了,万事俱备只欠东风
Java SPI 不足之处:
- 不能按需加载。Java SPI在加载扩大点的时候,会一次性加载所有可用的扩大点,很多是不须要的,会节约系统资源
- 获取某个实现类的形式不够灵便,只能通过 Iterator 模式获取,不能依据某个参数来获取对应的实现类
- 不反对AOP与IOC
- 如果扩大点加载失败,会导致调用方报错,导致追踪问题很艰难
2、Dubbo SPI
Dubbo从新实现了一套性能更强的SPI机制, 反对了AOP与依赖注入,并且利用缓存进步加载实现类的性能,同时反对实现类的灵便获取。
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.8</version></dependency>
外围类:org.apache.dubbo.common.extension.ExtensionLoader
先来理解一下@SPI注解,@SPI是用来标记接口是一个可扩大的接口
革新一下后面的例子,在Car接口上加上@SPI注解
package org.example;import org.apache.dubbo.common.extension.SPI;@SPIpublic interface Car { void run();}
两个实现类不变
在META-INF/dubbo目录下创立名为org.example.Car的文本文件,内容如下(键值对模式):
toyota=org.example.ToyotaCarhonda=org.example.HondaCar
编写测试类:
package org.example;import org.apache.dubbo.common.extension.ExtensionLoader;import java.util.ServiceLoader;public class App{ public static void main( String[] args ) { // Java SPI ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); // Dubbo SPI ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); Car car = extensionLoader.getExtension("honda"); car.run(); }}
上面跟一下代码
如果缓存Map中有,间接返回,没有则加载完当前放进去
加载策略到底是怎么的呢?
到这里就有点明确了,又看到了相熟的ServiceLoad.load(),这不是方才讲的Java SPI嘛
回到之前策略那个中央,将策略按顺序排列,顺次遍历所有的策略来加载。就是在那三个目录下查找指定的文件,并读取其中的内容
跟之前的ServiceLoader一模一样
遇到@Adaptive标注的就缓存起来
最初,大家关注公众号Java技术栈,在后盾回复:面试,能够获取我整顿的 Java、Dubbo 系列面试题和答案,十分齐全。
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2021最新版)
2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!