🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
vivo 互联网服务器团队 - Ma Jian
一、概述
SPI(Service Provider Interface),是Java内置的一种服务提供发现机制,可以用来提高框架的扩展性,主要用于框架的开发中,比如Dubbo,不同框架中实现略有差异,但核心机制相同,而Java的SPI机制可以为接口寻找服务实现。SPI机制将服务的具体实现转移到了程序外,为框架的扩展和解耦提供了极大的便利。
得益于SPI优秀的能力,为模块功能的动态扩展提供了很好的支撑。
本文会先简单介绍Java内置的SPI和Dubbo中的SPI应用,重点介绍分析Spring中的SPI机制,对比Spring SPI和Java内置的SPI以及与 Dubbo SPI的异同。
二、Java SPI
Java内置的SPI通过java.util.ServiceLoader类解析classPath和jar包的META-INF/services/目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。
2.1 Java SPI
先通过代码来了解下Java SPI的实现
① 创建服务提供接口
package jdk.spi;
// 接口
public interface DataBaseSPI {
public void dataBaseOperation();
}
② 创建服务提供接口的实现类
- MysqlDataBaseSPIImpl
实现类1
package jdk.spi.impl;
import jdk.spi.DataBaseSPI;
public class MysqlDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Operate Mysql database!!!");
}
}
- OracleDataBaseSPIImpl
实现类2
package jdk.spi.impl;
import jdk.spi.DataBaseSPI;
public class OracleDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Operate Oracle database!!!");
}
}
③ 在项目META-INF/services/目录下创建jdk.spi.DataBaseSPI文件
jdk.spi.DataBaseSPI
jdk.spi.impl.MysqlDataBaseSPIImpl
jdk.spi.impl.OracleDataBaseSPIImpl
④ 运行代码:
JdkSpiTest#main()
package jdk.spi;
import java.util.ServiceLoader;
public class JdkSpiTest {
public static void main(String args[]){
// 加载jdk.spi.DataBaseSPI文件中DataBaseSPI的实现类(懒加载)
ServiceLoader dataBaseSpis = ServiceLoader.load(DataBaseSPI.class);
// ServiceLoader实现了Iterable,故此处可以使用for循环遍历加载到的实现类
for(DataBaseSPI spi : dataBaseSpis){
spi.dataBaseOperation();
}
}
}
⑤ 运行结果:
Operate Mysql database!!!
Operate Oracle database!!!
2.2 源码分析
上述实现即为使用Java内置SPI实现的简单示例,ServiceLoader是Java内置的用于查找服务提供接口的工具类,通过调用load()方法实现对服务提供接口的查找(严格意义上此步并未真正的开始查找,只做初始化),最后遍历来逐个访问服务提供接口的实现类。
上述访问服务实现类的方式很不方便,如:无法直接使用某个服务,需要通过遍历来访问服务提供接口的各个实现,到此很多同学会有疑问:
- Java内置的访问方式只能通过遍历实现吗?
- 服务提供接口必须放到META-INF/services/目录下?是否可以放到其他目录下?
在分析源码之前先给出答案:两个都是的;Java内置的SPI机制只能通过遍历的方式访问服务提供接口的实现类,而且服务提供接口的配置文件也只能放在META-INF/services/目录下。
ServiceLoader部分源码
public final class ServiceLoader ~~implements Iterable~~{
// 服务提供接口对应文件放置目录
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class ~~service;
// 类加载器
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// 按照初始化顺序缓存服务提供接口实例
private LinkedHashMap providers = new LinkedHashMap<>();
// 内部类,实现了Iterator接口
private LazyIterator lookupIterator;
}~~~~~~
从源码中可以发现:
- ServiceLoader类本身实现了Iterable接口并实现了其中的iterator方法,iterator方法的实现中调用了LazyIterator这个内部类中的方法,解析完服务提供接口文件后最终结果放在了Iterator中返回,并不支持服务提供接口实现类的直接访问。
- 所有服务提供接口的对应文件都是放置在META-INF/services/目录下,final类型决定了PREFIX目录不可变更。
所以Java内置的SPI机制思想是非常好的,但其内置实现上的不足也很明显。
三、Dubbo SPI
Dubbo SPI沿用了Java SPI的设计思想,但在实现上有了很大的改进,不仅可以直接访问扩展类,而且在访问的灵活性和扩展的便捷性都做了很大的提升。
3.1 基本概念
① 扩展点
一个Java接口,等同于服务提供接口,需用@SPI注解修饰。
② 扩展
扩展点的实现类。
③ 扩展类加载器:ExtensionLoader
类似于Java SPI的ServiceLoader,主要用来加载并实例化扩展类。一个扩展点对应一个扩展加载器。
④ Dubbo扩展文件加载路径
Dubbo框架支持从以下三个路径来加载扩展类:
- META-INF/dubbo/internal
- META-INF/dubbo
- META-INF/services
Dubbo框架针对三个不同路径下的扩展配置文件对应三个策略类:
- DubboInternalLoadingStrategy
- DubboLoadingStrategy
- ServicesLoadingStrategy
三个路径下的扩展配置文件并没有特殊之处,一般情况下:
- META-INF/dubbo对开发者开放
- META-INF/dubbo/internal 用来加载Dubbo内部的扩展点
- META-INF/services 兼容Java SPI
⑤ 扩展配置文件
和Java SPI不同,Dubbo的扩展配置文件中扩展类都有一个名称,便于在应用中引用它们。
如:Dubbo SPI扩展配置文件
#扩展实例名称=扩展点实现类
adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler
3.2 Dubbo SPI
先通过代码来演示下 Dubbo SPI 的实现。
① 创建扩展点(即服务提供接口)
扩展点
package dubbo.spi;
import org.apache.dubbo.common.extension.SPI;
@SPI // 注解标记当前接口为扩展点
public interface DataBaseSPI {
public void dataBaseOperation();
}
② 创建扩展点实现类
- MysqlDataBaseSPIImpl
扩展类1
package dubbo.spi.impl;
import dubbo.spi.DataBaseSPI;
public class MysqlDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Dubbo SPI Operate Mysql database!!!");
}
}
- OracleDataBaseSPIImpl
扩展类2
package dubbo.spi.impl;
import dubbo.spi.DataBaseSPI;
public class OracleDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Dubbo SPI Operate Oracle database!!!");
}
}
③在项目META-INF/dubbo/目录下创建dubbo.spi.DataBaseSPI文件:
dubbo.spi.DataBaseSPI
#扩展实例名称=扩展点实现类
mysql = dubbo.spi.impl.MysqlDataBaseSPIImpl
oracle = dubbo.spi.impl.OracleDataBaseSPIImpl
PS:文件内容中,等号左边为该扩展类对应的扩展实例名称,右边为扩展类(内容格式为一行一个扩展类,多个扩展类分为多行)
④ 运行代码:
DubboSpiTest#main()
package dubbo.spi;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboSpiTest {
public static void main(String args[]){
// 使用扩展类加载器加载指定扩展的实现
ExtensionLoader dataBaseSpis = ExtensionLoader.getExtensionLoader(DataBaseSPI.class);
// 根据指定