Dubbo(一)_Java_SPI

发布时间 2023-07-29 23:52:19作者: Stitches

什么是 SPI?

Dubbo 的源码中大量涉及了 Java SPI设计思想,所以理解 SPI对理解 Dubbo源码有很大帮助。

Java SPI全称 Java Service Provider Interface,是 Java 提供的一种服务提供者发现机制。其核心功能是通过接口找到其实现类。在实际运用中,主要用在程序启动或者运行时,通过 SPI 机制,加载并装配接口的实现类,实现组件替换和动态扩展


数据库驱动——SPI

在使用 MySQL/Oracle 数据库时,只需要引入 MySQL驱动 jar包或者 Oracle驱动 jar包就可以了,它的实现关键点就是 DriverManager

  1. DriverManager 通过 SPI机制加载不同厂商的驱动;
  2. DriverManger 使用厂商的驱动获取连接。
//DriverManager使用SPI加载Driver扩展实现,如com.mysql.jdbc.Driver
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

//DriverManager使用加载到的厂商驱动获取连接。
Connection con = aDriver.driver.connect(url, info);

这里 ServiceLoader.load(xxx.class) 实现了 Driver 的加载,它会加载我们引入的 mysql-connector-java.jar 里的 /META-INF/services/java.sql.Driver 文件,该文件里的内容为 com.mysql.jdbc.Driver

这样做用什么好处呢?

  • JDK 数据库连接操作和驱动的实现彻底解耦;
  • 使用方只需要关心自己使用的数据库驱动,如果用 MySQL 的驱动,就不用管 Oracle 的驱动;
  • 使用方不需要加载所有的数据库驱动;
  • 使用方几乎不需要配置,只是切换数据库驱动。

使用 Java SPI设计协议组件

根据数据库驱动的例子,我们知道了 Java SPI 的基本用法:

  • 在 jar 包的META-INF/services下面,创建一个接口全限定名的文件;
  • 在接口全限定名文件中,逐行填写具体的实现类;
  • 使用方引入 jar 包;
  • 使用ServiceLoader.load加载实现类;
  • 遍历获取实现类。

假定有这样一个需求:需要实现一个通信软件,它支持 TCP/HTTP 协议,并且使用者能够在两个协议中自由切换,如果只使用 TCP协议,就不需要关注 HTTP协议。

可以按照以下思路实现:

  • 抽象协议接口模块,里面包含 IProtocol协议接口和 RequestUtil对协议调用的封装;
  • 新建 HTTP 协议实现包,用于实现 HTTP 协议,可以把它想象成 MySQL 的驱动包;
  • 新建 TCP 协议实现包,用于实现 TCP 协议,可以把它想象成 Oracle 的驱动包;
  • 使用时,只需要引入自己用到的协议包即可。

新建协议接口包

  • 新建 IProtocol 接口,用于协议的抽象;
  • 新建 RequestUtil,里面使用 SPI 自动加载协议实现类。
/**
 * 通讯协议接口
 */
public interface IProtocol {
    //发送请求
    void sendRequest(String message);
}


// 工具类
public class RequestUtil {

    //根据依赖倒置原则,这里依赖的是IProtocol接口,而不是具体的实现类。
    private IProtocol protocol;

    public void sendRequest(String message){
        //根据里氏替换原则,这个protocol可以使用子类HttpProtocol或TcpProtocol替换
        protocol.sendRequest(message);
    }

    //获取RequestUtil实例
    private static RequestUtil requestUtil;
    public static RequestUtil getInstance(){
        if(requestUtil == null){
            requestUtil = new RequestUtil();
        }
        return requestUtil;
    }

    public RequestUtil() {
        //初始化时,使用Java SPI初始化具体的协议实现类
        ServiceLoader<IProtocol> protocols = ServiceLoader.load(IProtocol.class);
        Iterator<IProtocol> iterator = protocols.iterator();
        if(iterator.hasNext()){
            protocol = iterator.next();
        }
    }
    public RequestUtil(IProtocol protocol) {
        this.protocol = protocol;
    }
    public IProtocol getProtocol() {
        return protocol;
    }

    public void setProtocol(IProtocol protocol) {
        this.protocol = protocol;
    }
}

新建 HTTP 协议实现包

  • 新建 HttpProtocol 实现类,实现 IProtocol 接口;
  • 新建 resources/META-INF/services/com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.IProtocol 文件,并在文件中填写 com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.http.HttpProtocol 项目结构。
/**
 * Http通讯协议
 */
public class HttpProtocol implements IProtocol {
    @Override
    public void sendRequest(String message) {
        //示意代码,省略实现细节
        System.out.println("使用Http发送消息:" + message);
    }
}

同理新建 TCP 协议实现包。

具体使用

  • 引入将要使用的协议包(这里以 HTTTP为例)
<dependency>
  <groupId>com.yuqiao.deeplearningdubbo</groupId>
  <artifactId>java_spi_protocol_v2_http</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>
  • 调用 API
/**
 * 协议调用方
 */
public class JavaSpiProtocolInvoker {
    public static void main(String[] args) {
        RequestUtil.getInstance().sendRequest("hello!");
    }
}

SPI 实现原理分析

Java SPI 的核心实现类是 ServiceLoader,先调用 ServiceLoader.load 加载实现类,然后遍历获取实现类。代码如下:

//第一步:调用ServiceLoader.load加载实现类
ServiceLoader<IProtocol> protocols = ServiceLoader.load(IProtocol.class);
//第二步:通过遍历获取实现类
Iterator<IProtocol> iterator = protocols.iterator();
while (iterator.hasNext()){
    IProtocol protocol = iterator.next();
    System.out.println(protocol);
}

ServiceLoader 类

类的相关核心属性如下:

public final class ServiceLoader<S>
    implements Iterable<S>
{
    // 实现类的默认文件路径前缀
    private static final String PREFIX = "META-INF/services/";

    // 被加载的类或者接口(IProtocol)
    private final Class<S> service;

    // 实现类的类加载器,默认为调用load方法的线程的上下文类加载器
    private final ClassLoader loader;

    // 访问控制上下文,用于控制类加载器的权限
    private final AccessControlContext acc;

    // 缓存已经实例化后的实现类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒迭代器,支持遍历时懒加载
    private LazyIterator lookupIterator;

接下来对 ServiceLoader.load() 方法的具体步骤进行源码分析:

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        // 这里实例化了一个 ServiceLoader 对象
        return new ServiceLoader<>(service, loader);
    }

    // ServiceLoader 的构造函数
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

    // reload 清空缓存的已加载的实现类实例,初始化 LazyIterator 迭代器
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

可以发现上述代码,都只是做了初始化,并没有真正地加载实现类,实现类的加载在后面。

ServiceLoader 类实现了 Iterator 接口,重写了 iterator() 方法实现,代码如下:

public Iterator<S> iterator() {
    return new Iterator<S>() {
        // knowProviders 缓存已经实例化的实现类
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        // 如果实现类有缓存,直接返回;否则调用 LazyIterator 的 hasNext() 方法
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        // 不存在已加载的实现类,就调用 LazyIterator 的 next() 方法
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

LazyIterator 类

LazyIterator 类的核心属性如下:

private class LazyIterator
    implements Iterator<S>
{
    //要加载的类或者接口
    Class<S> service;
    //类加载器,加载文件资源和类
    ClassLoader loader;
    //存储加载到的文件资源(META-INF/services/接口或类全限定名)
    Enumeration<URL> configs = null;
    //从文件路径中加载到的实现类全限定名
    Iterator<String> pending = null;
    String nextName = null;
}

上面提到过,ServiceLoader类在调用 Iterator()接口时如果没有发现实现类缓存,就会调用 LazyIteratornextService()hasNextService()

hasNextService() 方法通过传入的类加载器加载了 META-INF/services/ 文件夹下的文件,文件内容为接口实现类的全限定类名,然后存储到 configs 变量中。

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            //全路径名=META-INF/services/ + 类或接口的全限定名
            String fullName = PREFIX + service.getName();
            //加载所有的全路径名文件
            //如:META-INF/services/com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.IProtocol
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        //解析文件中所有的实现类名
        //如:com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.tcp.TcpProtocol
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

nextService() 方法根据 hasNextService() 方法获取到的类名来加载类,并进行类的实例化,最后将实例化的对象保存到缓存中。