Dubbo系列4:SPI原理

1.什么是Java的SPI

就是将接口的实现类都写在一个默认的配置文件里,然后Java会自动加载该文件,然后创建出对应的对象,和Spring的bean配置实现了一样的功能。其实这个也是IOC和AOP一样的功能。不过这个是由Java的类装载器来做的,而Spring是自己做的。
装逼点说法是 :SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。
一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。
SPI机制的约定
在META-INF/services/目录中创建以Service接口全限定名命名的文件,该文件内容为Service接口具体实现类的全限定名,文件编码必须为UTF-8。
使用ServiceLoader.load(Class class); 动态加载Service接口的实现类。
如SPI的实现类为jar,则需要将其放在当前程序的classpath下。
Service的具体实现类必须有一个不带参数的构造方法。
看一个例子就知道了(注意每个文件的包名和位置):
新建一个接口和两个实现类:

package com.com.liuqingchao.spi;

public interface DemoService {
    public String sayHi(String msg);
}

两个实现类:

package com.com.liuqingchao.spi.impl;

import com.com.liuqingchao.spi.DemoService;

public class ChineseDemoServiceImpl implements DemoService {
    public String sayHi(String msg) {
        return "你好, "+msg;
    }
}

和
package com.com.liuqingchao.spi.impl;

import com.com.liuqingchao.spi.DemoService;

public class EnglishDemoServiceImpl implements DemoService {
    public String sayHi(String msg) {
        return "Hello, "+msg;
    }
}

然后在resource目录下创建目录META-INF/services/,然后创建文件com.com.liuqingchao.spi.DemoService,注意这个要和接口的位置和定义一致,否则就没有效果。
文件里的内容是:

com.com.liuqingchao.spi.impl.EnglishDemoServiceImpl
com.com.liuqingchao.spi.impl.ChineseDemoServiceImpl

之后创建测试类:

package com.com.liuqingchao.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class MainTest {
    public static void main(String[] args) {
        ServiceLoader<DemoService> serviceLoader = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> it = serviceLoader.iterator();
        while (it != null && it.hasNext()) {
            DemoService demoService = it.next();
            System.out.println("class:" + demoService.getClass().getName() + "***" + demoService.sayHi("World"));

        }
    }
}

这时候执行就显示:

class:com.com.liuqingchao.spi.impl.EnglishDemoServiceImpl***Hello, World
class:com.com.liuqingchao.spi.impl.ChineseDemoServiceImpl***你好, World

两个都加载到了,我们就可以根据需要来选择使用哪个了。

2.Dubbo里的SPI

Dubbo里的 SPI是什么呢?其实就是标记一个接口可以有多种实现方式,而这个实现是要经过配置文件来服务的,然后调用者通过url里的参数或者其他方式来指定使用哪一个。
本章首先看现有Dubbo加载机制的概况, 包括Dubbo所做的改进及部分特性。 其次看加载机制中已经存在的一些关键注解, 如@SPI、 ©Adaptive> ©Activateo然后介绍整个加载机制中最核心的ExtensionLoader的工作流程及实现原理。

2.1 介绍

Dubbo良好的扩展性与两个方面是密不可分的, 一是整个框架中针对不同的场景, 恰到好
处地使用了各种设计模式, 二就是本章要介绍的加载机制。 基于Dubbo SPI加载机制, 让整个
框架的接口和具体实现完全解耦, 从而奠定了整个框架良好可扩展性的基础。
在 Dubbo 中,基于 Dubbo SPI 机制加载,几乎每个重要的位置都有,例如 协议扩展、调用拦截扩展、引用监听扩展、暴露监听扩展、集群扩展、路由扩展、负载均衡扩展、合并结果扩展、注册中心扩展、监控中心扩展、扩展点加载扩展、动态代理扩展、编译器扩展、Dubbo 配置中心扩展、消息派发扩展、线程池扩展、序列化扩展、网络传输扩展、信息交换扩展、组网扩展、
Telnet 命令扩展、状态检查扩展、容器扩展、缓存扩展、验证扩展、日志适配扩展。详细见https://dubbo.apache.org/zh/docs/v2.7/dev/impls/
我们先理解 Dubbo SPI 产生的背景:Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。例如Dubbo 有很多的拓展点,例如 Protocol、Filter 等等。并且每个拓展点有多种的实现,例如 Protocol 有 DubboProtocol、InjvmProtocol、RestProtocol 等等。那么使用 JDK SPI 机制,会初始化无用的拓展点及其实现,造成不必要的耗时与资源浪费。
如果扩展点加载失败,连扩展点的名称都拿不到了。
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
针对上面的不足,Dubbo实现了一套自己的SPI。
看一个Dubbo版SPI的例子:
在目录META-INF/dubbo/internal下建立配置文件 com.test.spi.Printservice,文件内容如下:

impl=com.test.spi.PrintServiceImpl

创建接口Printservice:

@SPI("impl")
public interface Printservice {
void printlnfo();
)

创建实现类:

public class PrintServicelmpl implements Printservice ( -----
@Override
public void printlnfo() (
   System.out・println("hello world");
}

调用函数:

public static void main(String[] args) (  
//调用Dubbo SPI
PrintService printservice = ExtensionLoader.getExtensionLoader(PrintService.class)
.getDefaultExtension();

//通过 ExtensionLoader 获取接口PrintService.class 的默认实现
printService.printInfo();  //此处输出 PrintServicelmpl 打印的 hello world

Dubbo SPI自己实现了 IoC和AOP机制。 一个扩展点可以通过setter方法直接注入其他扩展的方法, T injectExtension(T instance)方法实现了这个功能。 另外 Dubbo支持包装扩展类, 推荐把通用的抽象逻辑放到包装类中, 用于实现扩展点的AOP特性。 举个例子, 我们可以看ProtocolFilterWrapper包装扩展了 DubboProtocol类, 一些通用的判断逻辑全部放在了 ProtocolFilterWrapper 类的 export方法中, 但最终会调用 DubboProtocol#export方法。 这和Spring的动态代理思想一样, 在被代理类的前后插入自己的逻辑进行增强, 最终调用被代理类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值