后面曾经讲过SPI的根本实现原理了,demo也根本实现了,再来说说SPI。

http://aphysia.cn/archives/jd...

背景:SPI是什么?
SPI,即是Service Provider Interface,是一种服务提供(接口实现)发现机制,能够通过ClassPath门路下的META-INF/Service文件查找文件,加载外面定义的类。
个别能够用来启用框架拓展和替换组件,比方在最常见的数据库连贯JDBC中,java.sql.Driver,不同的数据库产商能够对接口做不一样的实现,然而JDK怎么晓得他人有哪些实现呢?这就须要SPI,能够查找到接口的实现,对其进行操作。
用两个字解释:解耦

再简略点说?
就是Java外围包不晓得第三方的包会怎么实现一个接口,定义了一个规定:你要对这个类拓展,那你就把你的实现类配置到一个文件外面,文件名就是你要拓展的接口,这样子,我只有用ServiceLoader加载接口,我就能够获取到实现类的实例。

对于java外围包来说,我不晓得你要怎么实现接口,然而只有你按我说的做,配置好,我就能保障你只有引入你本人的包,我就能够运行到你的代码。

外围代码如下:

    ServiceLoader<DBConnectionService>  serviceLoader= ServiceLoader.load(DBConnectionService.class);

所以咱们此时假如本人对ServiceLoader曾经非常好奇了,这是什么?这是怎么实现的?这么牛逼?

那就看源码?夜深人静刚刚好,白天也看不下去。

这里须要留神的是,这个ServiceLoader是一个泛型类,实现了Iterable,阐明了什么?阐明它的性能有一部分和汇合是差不多的,能够将多个服务的实现类加载在外面!!!能够通过遍历的形式,一一取出来

先看看ServiceLoader的类成员接口,不急着看load()函数:

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

当初来看load()函数,其实外面调用的也还是serviceLoader自身的结构器,两个load办法

  • 一个只须要传入须要实现的服务接口service
  • 另一个则是须要同时传入类加载器loader
    // 以后线程的类加载器作为默认加载器    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);    }

咱们还是来看serviceLoader的结构器:

    private ServiceLoader(Class<S> svc, ClassLoader cl) {        // 要加载的接口        service = Objects.requireNonNull(svc, "Service interface cannot be null");        // 加载器,如果为null则默认应用零碎加载器进行加载        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;        // 管制拜访器        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;        // 从新加载        reload();    }

看从新加载的办法:

    public void reload() {        // 清空曾经加载的服务类        providers.clear();        // 初始化查找加载类的迭代器        lookupIterator = new LazyIterator(service, loader);    }

查找加载类的迭代器,到底是什么?从名字来看,是一个懒加载器,就是提早加载,从名字来看,大略能猜到,这个就是应用的时候才加载,真的是这样么???接着看上来:

下面 ???? 咱们说到ServiceLoader其实是一个泛型类,实现了Iterator接口,阐明它能够被遍历,遍历的元素是什么呢?就是下面所说的成员变量LinkedHashMap<String,S> providers,实现的服务都加载在外面了。

那咱们就看看遍历的时候怎么取的?
这就波及到了Iterable接口的办法foreach()了,咱们就来看看。
总所周知,Iterable接口的办法如下:

    Iterator<T> iterator();    default void forEach(Consumer<? super T> action) {        Objects.requireNonNull(action);        for (T t : this) {            action.accept(t);        }    }    default Spliterator<T> spliterator() {        return Spliterators.spliteratorUnknownSize(iterator(), 0);    }

那我猜遍历的办法应该被ServiceLoader实现的时候,曾经重写了,果不其然:
看获取Iterator的办法实现,咱们能够发现一个惊天㊙️密:也就是其实咱们遍历的时候,优先是应用已知的汇合迭代器,这个汇合,就是存储咱们的服务提供者的汇合,也就是曾经加载的服务类汇合。如果这个汇合曾经遍历实现的时候,就会调用查找迭代器去查找,不论next()还是hasNext()办法,都是这样的。
同时曾经加载的服务,是不能够被移除的,为了避免这一点,在移除的时候会返回异样。

    public Iterator<S> iterator() {        return new Iterator<S>() {            //  被查找到的已知的服务提供者的迭代器            Iterator<Map.Entry<String,S>> knownProviders                = providers.entrySet().iterator();            // 如果已知的迭代器,也就是存储服务提供者的汇合迭代器,如果这个有下一个元素,那么就会间接返回true,如果没有那么就会调用查找迭代器的hasNext()            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的其余办法呢?

当然是用了默认实现了,其余两个办法都加了default关键字,ServiceLoader没有去实现它,能够不实现,用默认实现就能够。

所以咱们的重点是什么?当然是这个lookupIterator,它可是一个提早加载器,为什么这么说,我感觉应该和下面的剖析无关,先遍历曾经加载的,而后没有了,才会应用这个提早查找迭代器,从它的名字就能够很分明的看进去,这其实就是一个查找的迭代器,他人都是迭代遍历曾经存在的元素,它倒好,懒到肯定水平了,用来查找。

废话少说,间接看看它怎么实现的,这么???? 牛!
在回头看看后面初始化的时候,结构是这样子的:

        // 初始化查找加载类的迭代器        lookupIterator = new LazyIterator(service, loader);

能够看到其实是将须要加载的服务接口以及类加载器传递进来了。
代码精简,看????上面的代码加注解,应该很清晰了。

    private class LazyIterator        implements Iterator<S>    {        // 须要加载的服务        Class<S> service;        // 类加载器        ClassLoader loader;        // 实现类的url(多个)        Enumeration<URL> configs = null;        // 实现类的全名(多个)        Iterator<String> pending = null;        // 迭代器中下一个实现类的全名        String nextName = null;        // 结构器其实没有什么操作,单纯保留        private LazyIterator(Class<S> service, ClassLoader loader) {            this.service = service;            this.loader = loader;        }        // 获取下一个        public S next() {            // 如果管制拜访器是空的            if (acc == null) {                // 调用获取下一个元素                return nextService();            } else {                // 特权动作,重写的其实也是调用nextService()                PrivilegedAction<S> action = new PrivilegedAction<S>() {                    public S run() { return nextService(); }                };                // 通过管制拜访器的特权拜访,跳过查看                return AccessController.doPrivileged(action, acc);            }        }        // 是否有下一个元素        public boolean hasNext() {            if (acc == null) {                // 如果拜访控制器是null,那么久间接调用hasNextService办法                return hasNextService();            } else {                // 生成特权动作                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {                    public Boolean run() { return hasNextService(); }                };                // 应用拜访控制器执行特权动作                return AccessController.doPrivileged(action, acc);            }        }        // 不能够移除        public void remove() {            throw new UnsupportedOperationException();        }        // 是否有下一个元素        private boolean hasNextService() {            // 如果下一个元素的全类名名字不为null,那么必定是有下一个元素            if (nextName != null) {                return true;            }            // 如果这个实现类的url是空的,怎么办?加载进去            if (configs == null) {                try {                    // 获取全名                    String fullName = PREFIX + service.getName();                    // 如果类加载器是null                    if (loader == null)                        // 通过全名,拿到全名的url,这个url其实就是依据名字找到的配置文件的门路                        configs = ClassLoader.getSystemResources(fullName);                    else                        // 否则调用loader本身的办法获取                        configs = loader.getResources(fullName);                } catch (IOException x) {                    fail(service, "Error locating configuration files", x);                }            }            // 如果实现类的全限定类名是空的,那就必定须要从文件外面读出来呀,因为文件名是接口,外面配置的是接口的实现类,pending保留的就是实现类            while ((pending == null) || !pending.hasNext()) {                // 如果须要读取的配置没有要读的了                if (!configs.hasMoreElements()) {                    // 间接返回false                    return false;                }                // 否则须要解析配置文件外面的内容                pending = parse(service, configs.nextElement());            }            // 下一个service的名字就更新为读取到的接口实现类,如果文件外面什么都没有配置,那就很有可能是空的。            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");            }            // 判断服务c是不是实现来自于service,两者是不是继承关系            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        }    }

通过下面的代码,其实咱们能够分明的看到,这个提早加载器,会去读取配置类,以及实现的的接口,将实现类全类名放到configs,而后通过反射的模式构建实例对象,实例化之后才放到providers中,而后返回实现类对象。

值得注意的是,如果拜访控制器是空的,那么就会调用特权执行:AccessController.doPrivileged(action, acc);,获取到服务实现的时候,也会判断是不是实现来自于咱们须要实现的接口,否则会报错,调用的是service.isAssignableFrom(c)

下面还有一段解析配置的代码没有阐明,补上:

    private Iterator<String> parse(Class<?> service, URL u)        throws ServiceConfigurationError    {        // 输出流        InputStream in = null;        // buffer读入        BufferedReader r = null;        ArrayList<String> names = new ArrayList<>();        try {            in = u.openStream();            r = new BufferedReader(new InputStreamReader(in, "utf-8"));            int lc = 1;            // 依照每一行来读入            while ((lc = parseLine(service, u, r, lc, names)) >= 0);        } catch (IOException x) {            fail(service, "Error reading configuration file", x);        } finally {            try {                if (r != null) r.close();                if (in != null) in.close();            } catch (IOException y) {                fail(service, "Error closing configuration file", y);            }        }        // 返回的其实是实现类全限定名的汇合迭代器        return names.iterator();    }    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,                          List<String> names)        throws IOException, ServiceConfigurationError    {        String ln = r.readLine();        if (ln == null) {            return -1;        }        // 对于#号前面的内容不加载,间接疏忽掉        int ci = ln.indexOf('#');        if (ci >= 0) ln = ln.substring(0, ci);        ln = ln.trim();        int n = ln.length();        if (n != 0) {            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))                fail(service, u, lc, "Illegal configuration-file syntax");            int cp = ln.codePointAt(0);            if (!Character.isJavaIdentifierStart(cp))                fail(service, u, lc, "Illegal provider-class name: " + ln);            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {                cp = ln.codePointAt(i);                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))                    fail(service, u, lc, "Illegal provider-class name: " + ln);            }            // 没有加载过才会增加进去            if (!providers.containsKey(ln) && !names.contains(ln))                names.add(ln);        }        return lc + 1;    }

到这里,serviceLoader的内容就解读结束了,思维挺好的,有两个迭代器,一个是提供服务的汇合自身的迭代器,迭代实现之后,才会去应用提早调用lookup迭代器,触发寻找操作,如果查找了,那么就加载到汇合中,下次就不必再找了。

查找的时候,间接依据该门路下的文件,文件名就是接口,接口外面每一行都是接口的实现类。

实例化接口实现类的时候,其实是应用了反射,而后判断类型之后,类型转换成为⤴️上转型,放到曾经发现的服务汇合中,返回。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。这个世界心愿所有都很快,更快,然而我心愿本人能走好每一步,写好每一篇文章,期待和你们一起交换。

此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~