java SPI源码

92 阅读4分钟

1. 背景

翻到之前做的spi源码的一些笔记,想分享一下

2. 什么是SPI

Java的SPI全称是Service Provider Interface,它是Java提供的一套用来被第三方实现或者扩展的接口,可以用来启用框架扩展和替换组件。SPI的作用就是为这些被扩展的API寻找服务实现。它是一种服务发现机制,其核心思想就是解耦,这在模块化设计中尤其重要。我们可以看一下jdbc中spi的使用:

当我们打开jdk的源码之后,我们发现rt.jar下有一个java.sql.Driver的类,它定义了一些数据库操作的标准方法。然后mysql-connector-java中的com.mysql.jdbc实现了java.sql.Driver接口。同时在META-INF/services目录下放了java.sql.Driver文件:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

3. 一个demo

  • 定义一个ISpi接口
public interface ISpi {

    void print();
}
  • 实现类SpiA
public class SpiA implements ISpi {
    @Override
    public void print() {
        System.out.println(this.getClass().getCanonicalName());
    }
}
  • 实现类SpiB
public class SpiB implements ISpi {
    @Override
    public void print() {
        System.out.println(this.getClass().getCanonicalName());
    }
}
  • 测试方法
public static void main(String[] args) {
    ServiceLoader<ISpi> spi = ServiceLoader.load(ISpi.class);
    for (ISpi iSpi : spi) {
        iSpi.print();
    }
}
  • 打印
com.example.spi.impl.SpiA
com.example.spi.impl.SpiB

4. 源码 java.util.ServiceLoader

4.1 关键参数

//目录
private static final String PREFIX = "META-INF/services/"; 
//定义的接口
private final Class<S> service; 
//类加载器
private final ClassLoader loader; 
private final AccessControlContext acc; 
//local cache
private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 
private LazyIterator lookupIterator;

4.2 load方法

  • load方法
public static <S> ServiceLoader<S> load(Class<S> service) {
    //获取当前线程的类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //继续调用load
    return ServiceLoader.load(service, cl);
}
  • load
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    //创建一个新的ServiceLoader
    return new ServiceLoader<>(service, loader);
}
  • 构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    //常规判空
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //传的类加载器为空,则使用系统类加载器,所以当想直接使用类加载器,就直接传null进来
    //应该就是这里破坏了双亲委派,因为直接用当前的类加载机器加载了,而没有双亲委派
    //是的,就是这里破坏了双亲委派,专业名称是:线程上下文类加载器,通过线程(Thread)类的setContextClassLoader()进行设置,未设置会从父线程继承,默认是应用程序类加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    //java的某种安全机制
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    //清空map容器和初始化迭代器
    reload();
}

  • reload 方法
public void reload() {
    //清空map容器,因为这个方法是用户可以调用的,不是private方法
    providers.clear();
    //初始化迭代器 -- 懒加载 
    // LazyIterator 的构造方法里没有处理逻辑只有赋值类加载器和类变量
    lookupIterator = new LazyIterator(service, loader);
}

4.3 编译后的main方法

我们可以看到编译后,实际调用的是哪个方法

public static void main(String[] args) {
    ServiceLoader<ISpi> spi = ServiceLoader.load(ISpi.class);
    Iterator var2 = spi.iterator();
    while(var2.hasNext()) {
        ISpi iSpi = (ISpi)var2.next();
        iSpi.print();
    }
}

4.4 iterator

//调用ServiceLoader 的iterator 方法获取一个新的迭代器
public Iterator<S> iterator() {
    return new Iterator<S>() {
        
        //map迭代器,那么这个map应该是一个local cache
        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();
        }
        
        //不支持remove
        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

4.4 hasNext方法

//首先看hashNext,map的就不看了
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);
    }
}

4.5 hasNextService 方法

private boolean hasNextService() {
    //nextName 不为空直接返回
    if (nextName != null) {
        return true;
    }
    //获取spi文件的url
    if (configs == null) {
        try {
            //META-INF/services/ + 全限定类名
            String fullName = PREFIX + service.getName();
            //类加载器获取全路径
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    //把文件的每一行作为一个类名读到pending 中
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

4.6 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);
    }
}

4.7 nextService

private S nextService() {
    //判空
    if (!hasNextService())
        throw new NoSuchElementException();
    //获取类名
    String cn = nextName;
    //置空,为了获取下一个
    nextName = null;
    
    Class<?> c = null;
    try {
        //获取类对象
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    //类校验
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    //实例化类后强制类型转换
    try {
        S p = service.cast(c.newInstance());
        //local cache ps:终于看到这个逻辑了
        providers.put(cn, p);
        //返回
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

5. 总结

写文章的过程中,也是重新复习了一下之前看的代码。