天天看点

JDK的SPI机制的应用和原理

作者:诗歌乐园
JDK的SPI机制的应用和原理

SPI的全名是Service Provider Interface,这个概念最初是针对于厂商或者插件的。java项目中,不同模块之间通过接口进行调用,接口会有很多的实现类,具体使用哪个,很多时候调用方并不想写死,写死也就是硬编码,这不符合可插拔的原则。就像IOC那样,spi机制将装配的控制权转移到程序之外,这种机制对于模块化编程非常重要。本文主要介绍下如何使用spi,以及SPI的实现原理

SPI的使用demo

俄罗斯进攻乌克兰作成接口,实现类有导弹进攻,坦克进攻等,绍伊古将这些进攻实现写到一个小本子上,他在什么时间,什么地点,采取什么样的进攻方式,不是固定不变的,是有选择性的,也许他会采取一种进攻方式,也可能把所有的进攻方式都用上,真正实现了打法的灵活多变。

进攻接口:

public interface Attack {
   void doAttack();
}           

实现类:

public class MissileAttack  implements Attack{

    @Override
    public void doAttack(){
        System.out.println("俄罗斯的导弹攻击");
    }
}

public class TankAttack implements Attack {

    public void doAttack(){
        System.out.println("俄罗斯的坦克攻击");
    }
}           

我们需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名:

JDK的SPI机制的应用和原理

绍伊古的小本子

通过serviceLoader加载实现类并调用:

public  static void main(String[] args){

    ServiceLoader<Attack> serviceLoader = ServiceLoader.load(Attack.class);
    for(Attack attack:serviceLoader){
        attack.doAttack();
    }
}           

在实际使用中,我们可以使用接口文件中配置的第一个实现类作为实际的执行者。也可以结合apollo配置,例如我们可以将实现类的序号配置到apollo中,来选择实际的执行者。

SPI的实现原理:

主要实现类为ServiceLoader,它实现了Iterable接口。

public void reload() {
    providers.clear();
    //这个迭代器是实现的核心所在
    lookupIterator = new LazyIterator(service, loader);
}

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

在创建ServiceLoader的时候,内部创建了LazyIterator迭代器。LazyIterator迭代器的next方法会创建出一个个实现类。下面我们重点看下LazyIterator迭代器内的hasNextService()方法和nextService()方法:

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            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);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    //获取下一个实现类的名称
    nextName = pending.next();
    return true;
}

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());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}
           
JDK的SPI机制的应用和原理