SPI机制
- SPI概念:SPI全称为Service Provider Interface,是一种服务提供发现机制,将接口定义与实现解耦,提升程序的可扩展性。
- JDK的SPI本质:其本质是将接口实现类的全限定名配置在
META-INF/services
目录下的文件中中,由服务加载器ServiceLoader
读取配置文件加载实现类,为在运行时,动态为接口加载实现类。 - Dubbo的SPI:Dubbo中并未使用JDK原生的SPI,而是借鉴SPI的思想,实现了自己的一套SPI机制。在原生的SPI特性基础上,优化并增加其他的特性,如增加对扩展点IoC和AOP的支持、不会一次性加载所有扩展点实现类。
1、JDK-SPI
- 实现:Jdk的SPI机制由
ServiceLoader
实现,开发者定义一个接口并提供实现类,同时在META-INF/services
目录下创建一个以接口的全限定名为文件名的文件,并将其对应的实现类全限定名按行添加到文件中,最后打成一个jar包。 - 使用:依赖上述打的jar包,在需要时,用
ServiceLoader
的load
方法加载对应的接口的实现类。
1.1、示例
- 定义一个接口
DemoService
,有两个实现类:
package com.leefox.Impl;
//接口定义
public interface DemoService {
String sayHello(String msg);
}
//实现类-ManServiceImpl
public class ManServiceImpl implements DemoService {
@Override
public String sayHello(String msg) {
return "man say: " + msg;
}
}
//实现类-WomanServiceImpl
public class WomanServiceImpl implements DemoService {
@Override
public String sayHello(String msg) {
return "woman say: " + msg;
}
}
在META-INF/services
文件夹下创建一个文件,名称为 DemoService 的全限定名 com.leefox.Impl.DemoService
。文件内容为实现类的全限定的类名,如下:
com.leefox.Impl.ManServiceImpl
com.leefox.Impl.WomanServiceImpl
- 使用:
@Test
public void test() {
ServiceLoader<DemoService> services = ServiceLoader.load(DemoService.class);
for (DemoService demoService : services) {
System.out.println(demoService.getClass().getName());
System.out.println(demoService.sayHello("hello spi"));
System.out.println("--------------");
}
}
- 输出结果
2、DUBBO-SPI
Dubbo 自己封装一套SPI加载机制,其逻辑被封装ExtensionLoader
类中。ExtensionLoader加载器需要约定其SPI实现类的目录在META-INF/dubbo
路径下。
2.1、示例
- 定义一个接口并加上1
@SPI
注解,有两个实现类。
//DubboSPIService接口定义
@SPI
public interface DubboSPIService {
String sayHello(String msg);
}
//实现类同Jdk SPI
在META-INF/dubbo
目录下增加一个接口全限定名的文件,并在文件中按照key-value的形式将实现类配置好。
manSPI=com.leefox.dubbo.ManServiceImpl
womanSPI=com.leefox.dubbo.WomanServiceImpl
- 使用
@Test
public void DubboSPI_test() {
//获取DubboSPIService对应的ExtensionLoader
ExtensionLoader<DubboSPIService> extensionLoader = ExtensionLoader.getExtensionLoader(DubboSPIService.class);
//通过key获取对应的实现类ManServiceImpl
DubboSPIService manSPI = extensionLoader.getExtension("manSPI");
System.out.println(manSPI.sayHello("hello META-INF spi"));
System.out.println("--------------");
//WomanServiceImpl
DubboSPIService womanSPI = extensionLoader.getExtension("womanSPI");
System.out.println(womanSPI.sayHello("hello META-INF spi"));
System.out.println("--------------");
}
- 结果
3、SPI vs API
3.1、定义
- SPI:Service Provider Interface
- SPI重点在:扩展实现功能,告诉你要遵从其定义规范
the SPI is the description of classes/interfaces/methods/... that you extend and implement to achieve a goal
- API:Application Programming Interface
- API重点在:调用使用功能,告诉你能获得什么功能?
the API is the description of classes/interfaces/methods/... that you call and use to achieve a goal
3.2、服务方和客户方角度
- 服务方暴露自己的业务供客户方调用,则为提供API服务。
- 客户方实现服务方提供的接口,然后让服务方去调用自己,则为提供SPI服务。
(别人博客的图)
作者自己的理解:
-
SPI可以理解为我定义了一个接口规范,
实现方
实现该接口来完成对应的功能,只要在接口定义的规范下,可以按照自己的需求来实现其想要的功能。然后给使用方
使用。 -
API可以理解为我定义了一个接口并实现了其功能,
使用方
拿来用就可以,但他能使用的功能只有我所实现的内容。
4、面试怎么描述呢?(重点)
- 首先SPI即Service Provider Interface,服务提供接口,比如你有个接口,改接口有 3 个实现类,那么在系统运行的时候到底选择哪个实现类呢?这就需要 spi 了,根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后执行实现类的功能。
- jdk 的spi
- dubbo采用了这种思想,但自己使用实现了一套spi机制,对应ExtensionLoader。相对于jdk 的spi来说,增加了一些特性,比如AOP,IoC,使用时才加载具体的实现类等等。
- 比如Protocol,默认情况下是dubbo,如果我们需要改变,那么首先自己实现Protocol接口,并在调用时指定自定义的name,会拼接在URL上,在通过ExtensionLoader加载时,因为Protocol接口内部的方法上加上了@Adaptive,自适应扩展注解,会动态生成代理类,其中会解析传入的URL,得到我们自定义的实现类,并调用其方法。
号外号外:
期待接下来结合在日常开发使用中dubbo的经历,学习看Dubbo
这一个神级源码,抽象程度已出神入化~