天天看点

从源码全面解析 Java SPI 的来龙去脉

一、引言

对于 Java 开发者而言,关于 dubbo ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

虽然现在是互联网寒冬,但乾坤未定,你我皆是黑马!

废话不多说,发车!

二、SPI是什么

SPI 全称 Service Provider Interface ,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。

从源码全面解析 Java SPI 的来龙去脉

Java SPI 实际上是 基于接口的编程+策略模式+配置文件 组合实现的动态加载机制。

Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。

将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

所以 SPI 的核心思想就是解耦。

三、使用介绍

我们定义一个接口 Phone:

package com.study.spring.spi;
public interface Phone {
    public void getName();
}           

实现其三个类:

  • HuaWei
package com.study.spring.spi;

public class HuaWei implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是华为手机");
    }
}           
  • IPhone
package com.study.spring.spi;

public class IPhone implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是苹果手机");
    }
}           
  • XiaoMi
package com.study.spring.spi;

public class XiaoMi implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是小米手机");
    }
}           

重点来了:我们要在 resources 文件夹下面建立一个路径:META-INF/services

然后我们建立一个 txt 名为:com.study.spring.spi.Phone,如下:

从源码全面解析 Java SPI 的来龙去脉

我们在这个文件中写上各实现类的路径:

com.study.spring.spi.HuaWei
com.study.spring.spi.IPhone
com.study.spring.spi.XiaoMi           

测试类:

package com.study.spring.spi;

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

public class JavaSPITest {
    public static void main(String[] args) {
        // 执行Java SPI的规范
        ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);
		// 获取迭代器
        Iterator<Phone> it = phoneServiceLoader.iterator();
        // 迭代遍历,输出集合中的所有元素
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}           

结果:

我是是华为手机
我是是苹果手机
我是是小米手机           

从这里我们可以看到,通过 SPI 的机制,我们可以获取当前接口类的实现

四、生产场景

相信大家在生产上都使用过 JDBC,没错,我们的 JDBC 实际上也使用了 SPI

我们看 DriverManager 的静态方法 loadInitialDrivers:

static {
     // 初始化加载
     loadInitialDrivers();
     println("JDBC DriverManager initialized");
}           

我们查看下 loadInitialDrivers 方法的代码:

private static void loadInitialDrivers() {
    String drivers;
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // SPI的加载机制
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // 迭代
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            }
            return null;
        }
    });   
}           

当然,这里我们需要引入下面的 MAVEN 依赖,不然 Driver.class 的实现类为空

<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.18</version>
</dependency>           

测试类:

package com.study.spring.spi;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class JDBCTest {
    public static void main(String[] args) {
        ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);

        for (Iterator<Driver> iterator = serviceLoader.iterator(); iterator.hasNext(); ) {

            Driver driver = iterator.next();

            System.out.println(driver.getClass().getPackage() + " ------> " + driver.getClass().getName());
        }
    }
}           

启动:

package com.mysql.cj.jdbc, JDBC, version 4.2 ------> com.mysql.cj.jdbc.Driver           

从这里的输出我们可以得出一个假设:我们引入的 JDBC 包里面,存在上述 SPI 机制的 txt 文件名称为: java.sql.Driver 且内容为:com.mysql.cj.jdbc.Driver

我们直接去引入的包里面搜索一下:

从源码全面解析 Java SPI 的来龙去脉

果然没错,接下来我们来一下 SPI 的原理

五、SPI运行原理剖析

从我们上面的示例中可以发现,SPI 的实现一共两行:

package com.study.spring.spi;

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

public class JavaSPITest {
    public static void main(String[] args) {
        // 执行Java SPI的规范
        ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);
		// 获取迭代器
        Iterator<Phone> it = phoneServiceLoader.iterator();
        // 迭代遍历,输出集合中的所有元素
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}           

我们挨个分析

1、服务初始化加载

ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程的类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    // svc:com.study.spring.spi.Phone
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    // AppClassLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    // 安全控制
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    // 重点
    reload();
}           

这里的重点还是在我们的 reload 中:

public void reload() {
    // 清空我们的服务提供者缓存
    providers.clear();
    // 延时迭代器,用于延迟加载服务提供者
    // 达到用需加载的目的
    lookupIterator = new LazyIterator(service, loader);
}

private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}           

2、获取迭代器

Iterator<Phone> it = phoneServiceLoader.iterator();

// 返回我们ServiceLoader自定义实现的迭代器
public Iterator<S> iterator() {
    return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext()){
                return knownProviders.next().getValue();
            }
            return lookupIterator.next();
        }

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

3、判断是否有数据

it.hasNext()
public boolean hasNext() {
    // 刚刚初始化knownProviders是空的
    if (knownProviders.hasNext())
        return true;
    // 
    return lookupIterator.hasNext();
}

public boolean hasNext() {
    // 是否有控制权限
    // 一般都是空的
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}           

重点还是在 hasNextService 方法中

private boolean hasNextService() {
    // 如果当前的Name不等于空直接返回
    if (nextName != null) {
        return true;
    }
    // 配置是否为空(第一次肯定为空)
    if (configs == null) {
        try {
            // 写死的:META-INF/services 加上接口的名字
            // META-INF/services/com.study.spring.spi.Phone
            String fullName = PREFIX + service.getName();
            // 加载配置
            // configs
            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.study.spring.spi.HuaWei
        // com.study.spring.spi.IPhone
        // com.study.spring.spi.XiaoMi
        pending = parse(service, configs.nextElement());
    }
    // 遍历当前的列表,返回第一次的遍历信息(com.study.spring.spi.HuaWei)
    nextName = pending.next();
    return true;
}           

4、结果输出

System.out.println(it.next());
public S next() {
    // 刚刚初始化knownProviders是空的
    if (knownProviders.hasNext())
        return knownProviders.next().getValue();
    return lookupIterator.next();
}

public S next() {
    // 是否有控制权限
    // 一般都是空的
    if (acc == null) {
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}           

重点还是在 nextService 方法中

private S nextService() {
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    // cn:com.study.spring.spi.HuaWei
    // 根据类加载器+类的名称得出类
    c = Class.forName(cn, false, loader);
	
    // 创建实例 
    S p = service.cast(c.newInstance());
    // 将实例放到缓存里面
    // key:com.study.spring.spi.HuaWei
    // value:HuaWei@773
    providers.put(cn, p);
    // 返回实例
    return p;
}           

六、SPI流程图

从源码全面解析 Java SPI 的来龙去脉

七、总结

鲁迅先生曾说:独行难,众行易,和志同道合的人一起进步。彼此毫无保留的分享经验,才是对抗互联网寒冬的最佳选择。

其实很多时候,并不是我们不够努力,很可能就是自己努力的方向不对,如果有一个人能稍微指点你一下,你真的可能会少走几年弯路。

继续阅读