一.ServiceLoader介绍
- 一个简单的加载服务提供者的设施。
- 系统分为两个角色:应用程序,服务提供者jar。在应用程序中通过ServiceLoader加载所需服务。
二.使用
- jar里面包含服务程序。
- jar META-INF文件夹下新建services文件夹,在services文件里新建文件,文件名为提供服务的接口的全限定名,文件内容为实现接口的类的全限定名,多个实现类时以行分割。见图。
- 应用程序调用服务:
ServiceLoader<NameServiceDescriptor> serviceLoader= ServiceLoader.load(NameServiceDescriptor.class);
- ServiceLoader实现了Iterator接口,迭代 ServiceLoader即可得到每个NameServiceDescriptor。
三.源码
import java.net.URL; import java.util.ServiceConfigurationError; public final class ServiceLoader<S> implements Iterable<S> { private static final String PREFIX = "META-INF/services/"; //一个类或者接口对于一个ServiceLoader private Class<S> service; //加载服务服务提供者的ClassLoader private ClassLoader loader; //缓存服务提供者,按服务提供者实例化顺序 private LinkedHashMap<String, S> providers = new LinkedHashMap<String, S>(); //ServiceLoader的迭代委托给了lookupIterator private LazyIterator lookupIterator; private ServiceLoader(Class<S> svc, ClassLoader cl) { service = svc; loader = cl; reload(); } public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); } /** * 返回URL对应的文件内容的Iterator,内容以行分割 * @param service 类名,传进去只为了异常说明更详细 * @param u 文件名对应的URL */ private Iterator<String> parse(Class service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<String>(); 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(); } //从文件里面一行一行读,放到names里面 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); } //如果该服务提供者未被实例化,并且names不包含 if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; } private static void fail(Class service, String msg, ...)throws ServiceConfigurationError{ throw new ServiceConfigurationError(service.getName() + ": " + msg); } private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null;//文件对应路径URL Iterator<String> pending = null;//文件内容,一行一行存放 String nextName = null;//当前调用hashNext方法时,就得到下一个文件内容。 private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } public boolean hasNext() { 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; } public S next() { if (!hasNext()) { throw new NoSuchElementException(); } String cn = nextName; nextName = null; try { //反射,关键所在 S p = service.cast(Class.forName(cn, true, loader) .newInstance()); //缓存已经实例化的服务提供者 providers.put(cn, p); return p; } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated: " + x, x); } throw new Error(); // This cannot happen } public void remove() { throw new UnsupportedOperationException(); } } //以延迟方式加载服务提供者 //首先迭代被缓存的服务提供者,然后以延迟方式加载和实例化所有剩余的服务提供者,依次将每个服务提供者添加到缓存。 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(); } }; } //实例化ServiceLoader 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<S>(service, loader); } //用当前类加载器的父加载器 public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } public String toString() { return "java.util.ServiceLoader[" + service.getName() + "]"; } }
四.Lookup
NetBeans使用的东西。和ServiceLoader提供一样的功能。具体参见http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/doc-files/lookup-api.html
五.为什么要使用
- 为什么不直接使用反射,得到服务提供者。因为ServiceLoader提供了缓存机制,因为Lookup提供了监听机制。还有没有其他原因?
相关推荐
RocketMQ 奇技淫巧之 ServiceLoader 源码解读 抓下来打包成了HTML文件, 方便离线观看
Java SPI 机制(SPI实战+ServiceLoader源码分析+SPI 应用场景+破坏双亲委派)
NULL 博文链接:https://wen866595.iteye.com/blog/1541386
NULL 博文链接:https://huangyunbin.iteye.com/blog/1883124
下面小编就为大家带来一篇详谈ServiceLoader实现原理。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
这个maven插件为Java 6中引入的ServiceLoader生成服务文件: : 用 例如: < groupId>eu.somatik.serviceloader-maven-plugin < artifactId>serviceloader-maven-plugin < version>1.3.1 < param>...
Java 的 SPI(服务提供者接口)和 java.util.ServiceLoader 使用的简单演示。 介绍 这个演示应用程序包括 6 个小 jar 项目。 云服务 演示伪“云服务”提供者。 为服务提供者定义spidemo.cloud.spi.Cloud接口。 ...
Spring Boot java.util.ServiceLoader 使用java.util.ServiceLoader示例Spring Boot应用程序在类路径上动态加载所有实现。 多项目Gradle构建 1个Spring Boot应用 1个SPI项目 2个SPI实施项目 有关java.util....
System源码java
Flex与后台交互的方法---基于WebService方法
漫画聚集器 Java ServiceLoader示例
赠送jar包:osgi-resource-locator-1.0.1.jar; 赠送原API文档:osgi-resource-locator-1.0.1-javadoc.jar; 赠送源代码:osgi-resource-locator-1.0.1-sources.jar; 赠送Maven依赖信息文件:osgi-resource-locator...
什么是 Java ServiceLoader? 如何拼写 SPI(服务提供者接口)? 用 Java 编程语言编写的服务可以由不同的服务提供者实现。 虽然服务通常是用接口或抽象类编写的,但服务提供者是实现这些接口服务的实现类。 java....
一个简单的演示项目,用于将ServiceLoader与本机图像一起使用。 用以下方法构建罐子: $ mvn package 使用以下命令在JVM上运行: $ java -jar target/ServiceLoaderTest-1.0-SNAPSHOT.jar 输出应为: services....
WMRouter是一款Android路由框架,基于组件化的...基于SPI (Service Provider Interfaces) 的设计思想,WMRouter提供了ServiceLoader模块,类似Java中的java.util.ServiceLoader,但功能更加完善。通过ServiceLoader可
首先通过ServiceLoader.load方法生成一个ServiceLoader实例 然后在遍历的过程中去解析文件中的内容,去生成具体的实现类 然后再去调用具体的实现方法 只有在用到某个实现类的时候才会去解析文件中的内容,而且是全部...
科图巴Cotuba是用Java实现的命令行应用程序(CLI),可将Markdown文件(.md)转换为PDF或EPUB格式的电子书。 每个.md文件都被视为不同的章节。 本章标题取自最大标题:Markdown中的# 。...cli/target ZIP 复制tema-...
服务加载器这是一个让你理解java服务加载器机制的例子
ServiceLoader<Say> serviceLoader = ServiceLoader.load(Say.class); Iterator<Say> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Say service = iterator.next(); System.out....