JDK源码分析之细说SPI机制之实现原理剖析

一言不合就贴概念:java

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有很多框架用它来作服务的扩展发现, 简单来讲,它就是一种动态替换发现的机制, 举个例子来讲, 有个接口,想运行时动态的给它添加实现,你只须要添加一个实现缓存

是否是说了这么多还不知道他到底是干什么的?我也是。。安全

不过给个人理解是,我只须要接口就够了,实现类我不去管,也不须要new,有点相似于IOC的模式,至于适用场景嘛,你们能够本身YY一下。。app

这时候就会有人问,有了SPI就不须要写实现类了吗?错。为何呢,且听我一一道来。框架

 首先引用一张图ide

 

""

那么能够看得出,首先咱们须要在Classpath中有一个叫META-INF/services的文件夹,至于为何是这个,以后在源码中会体现出来,暂时卖个关子测试

第二步就是建立接口类。ui

好比咱们弄一个叫DemoService的类。spa

/**
 * SPI Test
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public interface DemoService {

    void sayHello();
}

第三步就是建立该service对应的实现类。code

/**
 * SPI Test impl
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public class DemoServiceImpl implements DemoService {
    @Override
    public void sayHello() {
        System.out.println("hello SPI!!!!");
    }
}

第四步:咱们须要在 META-INF/services 中建立对应的映射文件(暂且叫映射文件吧), 我以为也比较形象

文件名称与接口类的全类名保持一致

我这里放到了 org.jdk.demo.java.util.spi.DemoService 这个下面,因此对应的文件名应该是 org.jdk.demo.java.util.spi.DemoService 

而后将实现类的路径配置到该文件中。

我这里对这个接口有2个实现类。那么能够写两个(原则上应该是能够写无数个的)

而后咱们写一个对应的测试类,用于测试咱们写的方法

/**
 * @author Autorun
 * Created by Autorun on 2018/1/26.
 */
public class DemoServiceTest {

    public static void main(String[] args) {
        ServiceLoader<DemoService> services = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> it = services.iterator();
        while (it.hasNext()) {
            DemoService service = it.next();
            service.sayHello();
        }
    }
}

在这里。咱们测试一下。

 

会发现。他把咱们实现的方法所有都调用了一遍。经过迭代器中调用sayHello方法

先贴属性

咱们看到了META-INF/services/ 因此,呵呵,知道为何必须写在这个目录下了吧?

而后咱们开始看看ServiceLoader.load(DemoService.class)这一步他到底作了什么,它是如何将咱们的每个实现类的方法执行起来的 

public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取上下文的类加载器。而后调用重载的方法(ServiceClass, ClassLoader)
        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 = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

能够看得出来,他初始化了ServiceLoader这个对象,并将类加载器和接口类赋值给私有变量并调用了reload方法。咱们看看reload方法作了什么

public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

它又建立了一个内部类,叫lazyIterator,经过名字咱们能够看得出他是一个迭代器对象的实现类。顺便说一嘴,这个类才是真正去调用SPI类的地方,咱们从上面本身写的测试代码中应该就明白了。

废话很少说,继续贴代码。。

咱们while中调用了hasNext方法。咱们看看它内部作了什么

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

第一次进来的时候acc确定是空的,由于没有看到有给它赋值的地方。

它调用了内部的hasNextService方法。

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // meta-inf/services/xxx.DemoService 全名称
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 实用类加载器加载classpath下的文件。
                        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 Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        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();
    }

继续看获取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);
            }
        }
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
        }

那么能够得出,他会把该文件中全部的行都读到,而且初始化,放入providers 的集合中。而后将该实例返回回去。

咱们自行调用sayHello方法。那就执行了对应的实现类代码啦

说到这里,咱们应该很清晰的对JDK本身提供的SPI机制有了详细的认知。那么,他有没有什么问题呢?

答案是确定的,

1:懒加载,这个没毛病。可是只要一调用next方法,就会把全部的实现类都加载进来。对咱们来说是不科学的。这也不是咱们须要的,由于在特定场合咱们只须要指定的类就够了,而不须要那么多实现类。

2:内部缓存没对外开放,也能够理解,安全。那代价就是咱们须要本身去将具体的实现类放入咱们的容器中。

 

总结: 

其实他的SPI作了以下事情:

1.根据对应的接口类去找META-INF/services/下的接口类名

2:经过service获取对应的迭代器,调用hasNext方法去将META-INF/services/下对应全限定名文件中的类解析出来。

3:调用next方法加载类,并将其缓存到provides的map中。

并将其对应的实现类返回

 

小做业(留给有心的人): 自行实现一套SPI机制,实现根据指定类去加载对应的实现类。并将其缓存起来。

最后欢迎你们加入 JAVA那些事 77174608 一块儿成长进步。。

相关文章
相关标签/搜索