乐趣区

关于java:SPI你认识吗

人生有涯,学海无涯

一、SPI 是什么

SPI 是绝对 API 而言的。

API 指的是利用对服务调用方提供的接口,用于提供某种服务、性能,面向的是服务调用方。

SPI 指的是利用对服务实现方提供的接口,用于实现某种服务、性能,面向的是服务实现方

二、SPI 的应用

2.1、创立服务接口

package com.jw.spi;

public interface Fruit {String getName();
}

2.2、创立多个服务实现

package com.jw.spi;

public class Apple implements Fruit {
    @Override
    public String getName() {return "apple";}
}
package com.jw.spi;

public class Banana implements Fruit {
    @Override
    public String getName() {return "Banana";}
}

这里的两个服务实现类,针对的是两个服务实现方,一方实现了 Apple,另一方实现了 Banana。

2.3、创立配置文件

在 resource 下创立 /META-INF/services 目录,在 services 目录下创立以服务接口全限定名为名称的文件:com.jw.spi.Fruit

文件内容为,以后服务实现的服务实现者类的全限定名

com.jw.spi.Apple

2.4、创立测试类

public class Test {public static void main(String[] args) {ServiceLoader<Fruit> s = ServiceLoader.load(Fruit.class);
        Iterator<Fruit> it = s.iterator();
        while(it.hasNext()){System.out.println(it.next().getName());
        }
    }
}

执行后果为:

 apple

三、SPI 的实现原理

SPI 的实现次要依附的就是 ServiceLoader 类。应用该类加载接口类型(例如:Fruit.class)

ServiceLoader<Fruit> s = ServiceLoader.load(Fruit.class);

尽管是一个 load 办法,然而并没有加载到指定的服务实现类,这里仅仅是对加载服务实现类做一些筹备工作:

上面剖析一下 ServiceLoader 类的源码:

public final class ServiceLoader<S>
    implements Iterable<S>
{
    // 扫描目录前缀
    private static final String PREFIX = "META-INF/services/";

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

    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;

    // 上下文对象
    private final AccessControlContext acc;

    // 依照实例化的程序缓存曾经实例化的类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private LazyIterator lookupIterator;
    
    
    // 创立 ServiceLoader    
    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) {
        // 为 service 赋值
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
           // 为 loader 赋值
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
           // 为 acc 赋值
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();}

    public void reload() {
        // 清空 providers 缓存
        providers.clear();
        // 为 lookupIterator 赋值,其实就是创立一个 LazyIterator 提早迭代器。lookupIterator = new LazyIterator(service, loader);
    }

    // 公有外部类,提供对所有的 service 的类的加载与实例化
    private class LazyIterator implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

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

而后创立迭代器:Iterator<Fruit> it = s.iterator();

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

    };
}

iterator 办法中采纳了匿名外部类的形式定义了一个新的迭代器,这个迭代器中每一个办法都是通过调用之前创立好的提早迭代器 lookupIterator 来实现的

最初就是进行迭代加载了。

while(it.hasNext()){System.out.println(it.next().getName());
}
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);
    }
}

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

hasNext 办法调用了提早迭代器的 hasNext 办法,外部调用了 hasNextService 办法,在这个办法中就会设法去找到指定名称(META-INF/services/+ 接口全限定名)的资源文件。并实现读取文件内容的操作。

而后执行 it.next() 操作,这个又会调用提早迭代器的对应办法 hasNext,外部调用了 nextService 办法,这个办法次要性能就是加载下面从文件中读取到的全限定名称示意的类。并生成实例,将实例保留到 providers 中。

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

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 中
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider" + cn + "could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen}

四、总结

对于 spi 的详解到此就完结了,总结下 spi 能带来的益处:

  • 不须要改变源码就能够实现扩大,解耦。
  • 实现扩大对原来的代码简直没有侵入性。
  • 只须要增加配置就能够实现扩大,合乎开闭准则。
退出移动版