首页 > 编程学习 > Java-SPI源码剖析

Java-SPI源码剖析

发布时间:2022/11/9 8:16:27

1、创建

//  SPI接口实现类 要加载的位置前缀    
private static final String PREFIX = "META-INF/services/";
 
// 要加载接口的class对象
    // The class or interface representing the service being loaded
private final Class<S> service;
// 加载器
    // The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// 权限访问控制
    // The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// 缓存 提供方的
    // Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒查找迭代器
    // The current lazy-lookup iterator
private LazyIterator lookupIterator;

上述为 ServiceLoader成员属性

那ServiceLoad.load 干了什么??

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)
    {
        // 创建ServiceLoader对象
        return new ServiceLoader<>(service, loader);
    }
private ServiceLoader(Class<S> svc, ClassLoader cl) {
        // 判断一下传入的接口class对象是否合法
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
         // 类加载器,如果线程的classLoad没有,默认采用SystemClassLoader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // 权限访问控制
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
  }
  public void reload() {
        // 先把之前的缓存清了
        providers.clear();
        // 创建懒迭代器对象。
        lookupIterator = new LazyIterator(service, loader);
    }

reload方法, 先是将缓存清了,又创建懒 迭代器对象。这个懒加载迭代器是ServiceLoader的一个内部类。

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

        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
        }

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

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

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

ServiceLoader.load核心是清除缓存,创建lazyInterator,其用来类加载,遍历SPI实现类

2、加载

加载是在 LazyIterator 中完成的, 而且是在当我们判断获取的时候才加载

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 {
                    // 拼接 路径   META-INF/services/spi接口名称
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                    	// 获取spi接口实现类url
                        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;
}

可以看出进入一个hasNextService方法。在hasNextService方法中,先是判断一下下一个的元素名有没有,有的话直接返回true。判断config ==null 这个第一次的时候会进入,拼接默认spi接口实现类存放的路径,形成一个url。接着就会解析这个文件,获得一个迭代器对象。这个url实际上就是spi接口文件地址

说白了,上述操作,就是想获取到要加载的指定SPI实现类文件,获取到文件,读取配置项,也即获取SPI接口实现类列表

在这里插入图片描述

 private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        // 存储 扩展实现类的接口的全类名
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            // 通过BufferedReader来一行一行的读取
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
             通过BufferedReader来一行一行的读取
            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();
    }

parse(service, configs.nextElement())方法。我们可以看出parse方法通过BufferedReader 一行行读取配置文件存入List中,最后返回List的迭代器。

加载的过程,就是找到SPI接口位置,读取SPI接口配置文件,获取其中的实现类

3、获取

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;
            // 设置null
            nextName = null;
            Class<?> c = null;
            try {
                 // 生成class对象
                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
}

获取SPI实现类对象,本质上通过Class.forname 进行类加载获取的,然后放入缓存。

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

        };
    }

经过第一次SPI类加载之后,后续所有遍历操作都直接从缓存中拿,除非重新进行ServiceLoader.load 重新读取SPI接口文件配置项,进行类加载

总结

SPI机制是java提供的扩展机制,主要用来为第三方应用进行扩展用的,自身服务只需要提供SPI接口,第三方应用自己实现SPI接口即可。

SPI原理无非是内部通过LazyInterator进行处理,先找到SPI配置文件地址,逐一读取配置项,进行类加载获取class。当然内部维护一套缓存机制provider,不需要每次都读取SPi配置文件,Class.ForName,优化性能

优点:

SPI可以说是一种插拔机制, 使用SPI可以实现解耦,可以使得调用者与服务者自由扩展,而不是耦合在一起,可以使应用程序能够根据业务需要启用框架扩展或者替换框架组件

参考:https://inetyoung.blog.csdn.net/article/details/96973216

Copyright © 2010-2022 dgrt.cn 版权所有 |关于我们| 联系方式