回顾经典 - 解密SPI技术

197 阅读2分钟

前言

  • spi的全称是【service provider】,是一个简单的服务加载机制,原理:在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回。
  • 在ServiceLoader中,提供了延迟按需实例化的机制,在加载器中,维护一个已经加载的服务列表缓存,每次调用iterator()方法时,会按照实例化顺序生成缓存的所有对象,然后懒加载并实例化剩余的提供者,并依次将每个provider添加到缓存中。可以通过reload()方法清除缓存。

官方文档:docs.oracle.com/javase/8/do…

举例

这是例子的代码结构
image.png

接口

public interface SpiService {
    String test();
}

实现类

public class SpiAImpl implements SpiService {
    @Override
    public String test() {
        return "AAAAAAAAAAAA";
    }
}

public class SpiBImpl implements SpiService {
    @Override
    public String test() {
        return "BBBBBBBBBBBBBB";
    }
}

META-INF/services配置文件内容

在配置文件中,列出SpiService所有的实现类。如果只想加载SpiBImpl,那么可以删除SpiAImpl文件行。

com.example.spi.impl.SpiAImpl
com.example.spi.impl.SpiBImpl

加载并执行

    @Test
    public void contextLoads() {
        ServiceLoader<SpiService> serviceLoader
                = ServiceLoader.load(SpiService.class);
        for (SpiService service : serviceLoader) {
            System.out.println(service.test());
        }
    }

输出结果

image.png

源码研究

源码量很少,只有几百行,其中还有大量的注释,这里不做细致的介绍,只关注涉及到的关键点。
PREFIX静态变量,定义了spi加载的位置;providers是服务提供类的缓存。

private static final String PREFIX = "META-INF/services/";
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

加载源码

每次调用ServiceLoader.load()方法,都会执行reload(),清除providers缓存,并实例化懒加载迭代器,在调用iterator.hasNextService()方法时,再加载当前接口配置文件中,所有的服务实现类。
image.png

懒加载执行

private boolean hasNextService() {
	// 获取文件地址
    String fullName = PREFIX + service.getName();
    // 加载资源文件
    Enumeration<URL>  configs = loader.getResources(fullName);
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        // 解析文件,BufferedReader按行读取
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

mysql驱动

mysql驱动也用到了SPI接口,在【DriverManager】类中,执行加载流程。
image.png

加载数据库驱动

DriverManager中,有个静态代码块,在类初始化时执行

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

在loadInitialDrivers方法中,会调用doPrivileged方法,来执行具体的加载动作。

AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Void run() {
		// 加载数据库驱动
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        try{
			// 实例化驱动实现类
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {
        // Do nothing
        }
        return null;
    }
});