本文为拉勾课程的学习笔记
SPI简介
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。
JDK中的SPI
Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通过SPI机制中约定好的信息进行查询相应的接口实现。
SPI遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全
限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个无参构造方法;
具体代码实现
项目的目录结构如下
首先创建一个新的项目
再创建一个接口定义的moudle
定义接口
public interface HelloService {
String sayHello();
}
复制代码
创建一个实现接口moudle
定义接口实现类
public class DogHelloService implements HelloService {
@Override
public String sayHello() {
return "wang wang";
}
}
复制代码
public class HumanHelloService implements HelloService {
@Override
public String sayHello() {
return "hello 你好";
}
}
复制代码
按照约定
在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名
主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM
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());
}
}
}
复制代码
Dubbo SPI
dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。
比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。Dubbo中已经存在的所有已经实现好的扩展点。
dubbo已经实现好的扩展点
dubbo中使用扩展点的方式
创建一个项目,目录结构如下
定义接口
@SPI
public interface HelloService {
String sayHello();
}
复制代码
定义实现类
public class DogHelloService implements HelloService{
@Override
public String sayHello() {
return "wang wang";
}
}
复制代码
public class HumanHelloService implements HelloService{
@Override
public String sayHello() {
return "hello 你好";
}
}
复制代码
创建一个META-INF.dubbo文件夹,创建以“接口全限定名”为命名的文件,内容的key为实现类的别名,value为实现类的全限定类名。
使用
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 result = extensionLoader.getExtension(extension).sayHello();
System.out.println(result);
}
}
}
复制代码
运行结果
Dubbo SPI中的Adaptive功能
Dubbo中的Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。通过
getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)这里同样采用相同的方式进行实现。
基于上面的代码进行修改
在接口中增加一个入参为URL的方法
@SPI("human") // 注解中的值表示默认实现类的别名,就是指定url的情况下 默认实现HumanHelloService 这个实现类
public interface HelloService {
String sayHello();
@Adaptive
String sayHello(URL url);
}
复制代码
实现类
public class DogHelloService implements HelloService{
@Override
public String sayHello() {
return "wang wang";
}
@Override
public String sayHello(URL url) {
return "wang url";
}
}
复制代码
public class HumanHelloService implements HelloService{
@Override
public String sayHello() {
return "hello 你好";
}
@Override
public String sayHello(URL url) {
return "hello url";
}
}
复制代码
使用
public class DubboAdaptiveMain {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/hello?hello.service=dog");
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
String msg = adaptiveExtension.sayHello(url);
System.out.println(msg);
}
}
复制代码
上面代码
URL url = URL.valueOf(“test://localhost/hello?hello.service=dog”);
中的参数,?前面的可以随便指定,因为这只是一个demo,?后面的hello.service 为接口HelloService的名字,= 后面的内容为文件中定义的key。
运行结果
如果不指定实现类的话,则实现默认的实现类