04. Dubbo中的SPI
1. SPI简介
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。
2. JDK中的SPI
Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通过SPI机制中约定好的信息进行查询相应的接口实现。
2.1 SPI遵循如下约定:
- 当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的jar包放在主程序的classpath中;
- 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
- SPI的实现类必须携带一个无参构造方法;
2.2 示例:
2.2.1 新建空白maven项目
创建空白项目——java_spi_demo
2.2.2 新建module,并定义接口
创建module——java_spi_demo_api
新建
package com.dxh.service;
public interface HelloService {
String sayHello();
}
2.2.3 新建服务提供者module,编写实现类
创建module——java_spi_demo_impl
pom中加入依赖
<dependency>
<groupId>com.dxh</groupId>
<artifactId>java_spi_demo_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
编写实现类
package com.dxh.service.impl;
import com.dxh.service.HelloService;
public class HumanHelloService implements HelloService {
@Override
public String sayHello() {
return "Hello 你好~";
}
}
在resource下建立文件夹——META-INF.services
在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名
新建文件com.dxh.service.HelloService
,
com.dxh.service.impl.HumanHelloService
2.2.4 新建主程序module,编写测试方法
新建module——java_spi_demo_main
新建测试方法
package com.dxh.test;
import com.dxh.service.HelloService;
import java.util.ServiceLoader;
public class JavaSpiMain {
public static void main(String[] args) {
final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
for (HelloService helloService:helloServices){
System.out.println(helloService.getClass().getName()+":"+helloService.sayHello());
}
}
}
2.2.5 运行测试
返回结果:com.dxh.service.impl.HumanHelloService:Hello 你好~
目录结构:
3. Dubbo中的SPI
dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。
比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。
Dubbo中已经存在的所有已经实现好的扩展点:
下图中则是Dubbo中默认提供的负载均衡策略:
3.1 Dubbo扩展点的使用
分为三个项目演示:
- 服务接口项目api
- 服务实现项目impl
- 主项目main
新建maven项目:dubbo_spi_demo
3.1.1 服务接口项目
- 新建module-
dubbo_spi_demo_api
- pom中增加依赖,用于定义接口时使用
@SPI
注解
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
- 新建接口
package com.dxh.service;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface HelloService {
String sayHello();
}
3.1.2 服务端实现项目
- 新建module-
dubbo_spi_demo_impl
- pom中增加依赖
<dependency>
<groupId>com.dxh</groupId>
<artifactId>dubbo_spi_demo_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 新建接口实现类
package com.dxh.service.impl;
import com.dxh.service.HelloService;
public class HumanServiceImpl implements HelloService {
@Override
public String sayHello() {
return "Hello 你好";
}
}
- 在resources中新建文件夹
META-INF/dubbo
,区别于JDK提供的spi的文件名META-INF/services
- 在文件夹中新建文件,目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名
human=com.dxh.service.impl.HumanServiceImpl
3.1.3 主程序项目
- 新建module-
dubbo_spi_demo_main
- pom中增加依赖
<dependency>
<groupId>com.dxh</groupId>
<artifactId>dubbo_spi_demo_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.dxh</groupId>
<artifactId>dubbo_spi_demo_impl</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 新建入口类
和JDK SPI不同,dubbo对其进行自我重新实现,需要借助ExtensionLoader。
package com.dxh;
import com.dxh.service.HelloService;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.Set;
public class DubboSpiMain {
public static void main(String[] args) {
//获取扩展加载器
ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
//遍历所有的扩展点 META-INF.dubbo
Set<String> extensions = extensionLoader.getSupportedExtensions();
for (String extension : extensions) {
String sayHello = extensionLoader.getExtension(extension).sayHello();
System.out.println(sayHello);
}
}
}
- 运行测试:Hello 你好
目录结构:
4. Dubbo自己做SPI的目的
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
- 如果有扩展点加载失败,则所有扩展点无法使用
- 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入