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


声明:本文转载自https://my.oschina.net/u/2488220/blog/1613785,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

一言不合就贴概念:

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

是不是说了这么多还不知道他究竟是干什么的?我也是。。

不过给我的理解是,我只需要接口就够了,实现类我不去管,也不需要new,有点类似于IOC的模式,至于适用场景嘛,大家可以自己YY一下。。

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

 首先引用一张图

 

""

那么可以看得出,首先我们需要在Classpath中有一个叫META-INF/services的文件夹,至于为什么是这个,之后在源码中会体现出来,暂时卖个关子

第二步就是创建接口类。

比如我们弄一个叫DemoService的类。

 /**  * SPI Test  * @author Autorun  * Created by Autorun on 2018/1/26.  */ public interface DemoService {      void sayHello(); } 

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

 /**  * 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 一起成长进步。。

本文发表于2018年01月27日 08:32
(c)注:本文转载自https://my.oschina.net/u/2488220/blog/1613785,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 640 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

一直到现在我才突然明白,我梦寐以求,是真爱和自由。

把爱情留给我身边最真心的姑娘,你陪我歌唱陪我流浪,陪我两败俱伤。

把青春献给身后那座辉煌的都市,为了这个美梦,我们付出着代价。

又是一年五一,祝我们工人阶级劳动节快乐! 今年被困在北京了,离境再入境需要隔离十五天。只能京津冀周边走一走了,想出去玩啊啊啊啊啊~

人活一辈子,不是一年两年。时间是有连续性的,做抉择的时候要多看几步。保持警惕,大丈夫有所为,有所不为。

快捷链接
网站地图
提交友链
Copyright © 2016 - 2020 Cion.
All Rights Reserved.
ICP备案:鲁ICP备19012333号-4.

鲁公网安备 37061302000383号.