ServiceLoader是jdk提供动态加载类的一种方式。可以使得用户能够在运行时动态解析目标文件夹下接口配置文件来动态加载相关类使得直接可以在运行时直接保证相关类的加载

在jdk的nio包下SelectorProvider给出来相应的使用方式。

private static boolean loadProviderAsService() {ServiceLoader<SelectorProvider> sl =ServiceLoader.load(SelectorProvider.class,ClassLoader.getSystemClassLoader());Iterator<SelectorProvider> i = sl.iterator();for (;;) {try {if (!i.hasNext())return false;provider = i.next();return true;} catch (ServiceConfigurationError sce) {if (sce.getCause() instanceof SecurityException) {// Ignore the security exception, try the next providercontinue;}throw sce;}}
}

在这个方法中selectorProvicer可以在运行时动态加载目标目录下实现了SelectorProvider接口的SelectorProvider,并选取第一个作为运行时的SelectorProvider用来提供Selector以便nio的开发。

可以看到第一步只是简单调用了load()静态方法,将目标接口以及当前的系统类加载器。

可以直接看load()方法。

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{return new ServiceLoader<>(service, loader);
}public void reload() {providers.clear();lookupIterator = new LazyIterator(service, loader);
}private ServiceLoader(Class<S> svc, ClassLoader cl) {service = svc;loader = cl;reload();
}

首先load()方法只是简单的将之前目标类和类加载器作为参数简单调用了ServiceLoader的构造方法,在构造方法中,只是将相应的参数存放,然后调用了reload()方法。首先会把当前用来缓存加载过的类作为LinkedHashMap的provider清空,然后通过构造方法生成一个lazyIterator。lazyIterator作为ServiceLoader的内部类,构造方法也是只需要两个参数,目标类和类加载器,同时类如其名,lazyIterator的构造方法也只是简单的将目标参数,真正的逻辑操作在调用时才开始。

在通过load()完成了ServiceLoader的构造方法并返回之后,SelectorProvider调用了其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()方法只是返回了一个新的Iterator()类,并且重写了hasNext()和next()和remove()方法。这里也并没有任何逻辑,而之后SelectorProvider立即调用了其hasNext()方法,lazyIterator也类如其名的完成了延迟加载类的使命。

那么可以看到,hasNext()方法也就是类真正开始加载的号角,只要在真正要用到相关类的时候,才会真的开始加载所需要用到的类。

首先,会从knownProviders中选择,可以看到,这个类是在Iterator初始化的时候给创建的,是直接取得了providers的迭代器,但是由于在一开始ServiceLoader的构造方法中已经将其clear了,那么显然此时的knownProviders已经没有任何可以取得的类了,那么导致会调用lazyIterator的hasNext()。

public boolean hasNext() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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()方法中,会首先判断是否已经存在过nextName,也就是下一个要加载类名,当然此时,并不存在,所以方法会往下走。Config是一个url的迭代器,此时想必也是不存在的,那么会构造目标文件名fullName。可以看到fullName在这里的全名是/src/META-INF/services加上目标类名,这个就是lazyIterator所要解析的目标配置文件。Pending作为字符串的迭代器来保存已经取得的类名,取得文件后,如果pending还没有任何数据就会调用parse()方法去解析配置文件的内容去保存需要的实现了目标接口或继承的目标类。

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

这里会获得目标配置文件的BufferReader,以便读取解析,用lc作为当前解析的行号,以保证目标配置文件的每一行被都被顺利解析,name 作为数组来存放已经读取的类名。具体的解析在parseLine()当中。

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

这里首先会逻辑取得文件的下一行,如果已经到了文件最后一行会返回lc为-1,这样在parse()方法中也会使得读取文件行数的循环被打破,标志这配置文件的读取完成。之后保证每行都是一个类名,通过保证不存在空格与/t。然后会逐个字符逐个字符的读取,保证每个字符都符合java规范的,并且每次都会根据字符动态调整读取的跨度,这样保证在这一行中,可以准确无误的读取到符合java规范的类名,读取完毕之后去providers和names数组去保证不会重复加载,只要这样,才会将读取到的类名作为字符串保存在数组中,之后返回lc+1,确保再读取下一行数据。

经过上面的步骤parse()方法已经读取完了相应的配置文件,并已经把相应的类名存放在了字符串迭代器pending中,把pending的next()存放在nextName中,作为下一个要加载的类名,并返回true。

public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();
}

当SelectorProvider中通过hasNext()得到true时,自然会调用Iterator()的next()方法,在其next()方法中,首先还会去knownProviders中去找,但显然之前的操作并没有对其进行任何操作,所以最后还是会到LazyIterator的next()方法中来。

public S next() {if (!hasNext()) {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
}

next()方法的实现就相当简单,降将刚刚的nextName的类名通过Class.forName()加载,然后通过newInstance()得到实例强转成一开始配置的接口类或者需要被继承的类,然后将其通过类名和实例作为键值对存放在作为map的providers中,这样之后若是在又调用到了ServiceLoader的iterator()方法,那么就会将其键值对迭代器赋给knownProvider,以便不用重复同一个类的重复加载。

这样,如果之前配置了相应的配置文件SelectorProviders就可以在这里动态加载相应的provider并获取实例,很方便。

jdk的ServiceLoader相关推荐

  1. 【java】浅析JDK中ServiceLoader的源码

    1.概述 转载:浅析JDK中ServiceLoader的源码 上一篇文章:深入探讨 Java 类加载器 2.ServiceLoader的使用 这里先列举一个经典的例子,MySQL的Java驱动就是通过 ...

  2. 数据量大了一定要分表,分库分表 Sharding-JDBC 入门与项目实战

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:juejin.im/post/684490418236581 ...

  3. rust大油井频率怎么用_数据量大怎么搞?当然是用这个了!

    前言 最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库分表实现.调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Ja ...

  4. dubbo 即是服务提供者又是消费者_Dubbo详细介绍与安装使用过程

    1 Dubbo介绍 1.1 dubbox简介 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. ...

  5. sharding分表后主键_分库分表【Sharding-JDBC】入门与项目实战

    最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库分表实现.调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Java分 ...

  6. Dubbo详细介绍与安装使用过程

    1 Dubbo介绍 1.1 dubbox简介 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. ...

  7. 【java】SPI机制详解

    1.概述 以前的文章:[SPI]java基础之SPI框架实现 转载:Java常用机制 - SPI机制详解 PI(Service Provider Interface),是JDK内置的一种 服务提供发现 ...

  8. 【java】java基础之SPI框架实现-整体设计

    1.美图 2.概述 参考:添加链接描述 看原文去,原文是一个系列 SPI的设计思路,下图围绕 SpiLoader 为中心,描述了三个主要的流程: load所有的spi实现 初始化选择器 selecto ...

  9. 数据量大了一定要分表,分库分表Sharding-JDBC入门与项目实战

    作者:六点半起床 juejin.im/post/6844904182365814797 最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库 ...

最新文章

  1. ubuntu16.04安装CUDA 8.0(很详尽,包括一些坑的解决方法)
  2. string.Join和Reverse的简单使用示例
  3. 如何正确清理C盘中DriverStore文件夹中文件?
  4. js给标签添加属性和值_jquery节点属性
  5. 卸载oracle11g步骤_oracle11g完全卸载步骤来了,你真的不来看看吗
  6. algorithm头文件下的next_permutation()
  7. scrapy 爬虫-爬美食节
  8. 滴滴 AI Labs 负责人叶杰平因个人原因即将离职!CTO 张博接任
  9. WPF 播放Flash
  10. c语言标准库内存分配监控,C语言的本质(25)——C标准库之内存管理
  11. 通达信完全加密指标破解 通达信完全加密公式源码提取 tni tne tn6还原源文件
  12. 计算机说课稿模板小学数学,小学数学优质说课稿模板
  13. Golang环境windows 设置 GOROOT 和 GOPATH
  14. 选择器的权重中对交集选择器,分组(并集)选择器,以及关系选择器的理解
  15. Javascript 中 atob/btoa
  16. 基于SSM的家政服务管理系统
  17. js日期格式转换Wed Mar 22 13:38:37 CST 2017 转换 为yyyy-mm-dd
  18. 今日头条18校招第一批算法笔试
  19. 第一次使用Maven,新建Maven项目时更新出错出现Unable to update maven configuration following project...
  20. 慕尼黑大学计算机语言学,慕尼黑大学,斯图加特大学和萨尔大学的计算语言学硕士如何选择?...

热门文章

  1. Python TypeError: descriptor '__init__' requires a 'super' object but received a 'str' 错误
  2. SpringCloud创建Gateway模块
  3. versions-maven-plugin插件批量修改版本号
  4. idea项目在maven projects中变灰色带有删除线的解决办法
  5. 宁夏公安打传销端窝点为春节保平安
  6. 前端每日实战 2018 年 9 月份项目汇总(共 26 个项目)
  7. Radware LP 增加线路接口操作
  8. VisualSVNServerTools(在线修改VisualSVN密码)
  9. Sublime 下配置vim模式 + VintageEx-master下载地址
  10. Daily tips-7月