一.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提供了监听机制。还有没有其他原因?
相关推荐
这个标题提到的"System源码java-serviceloader-migration"是一个项目,专注于研究如何将`ServiceLoader`的使用迁移到Java 9及更高版本的模块系统中。Java 9引入了模块化系统,对程序的结构和依赖管理进行了重大改进...
通过对Settings应用源码的深入学习,开发者不仅可以掌握Android系统的内在工作原理,还能了解到如何构建高效、可扩展的设置界面,这对于提升自己的Android开发技能具有重要意义。同时,这也为定制化系统设置提供了...
标签“源码”意味着博主可能深入解析了`ServiceLoader`的内部工作原理,包括类加载器如何参与到服务的加载过程,以及源代码中的关键方法如`load()`、`iterator()`等的工作细节。此外,“工具”标签可能意味着博主还...
Java软件插件开发模式是一种灵活的...通过研究这个项目,你可以学习如何设计和实现一个Maven插件,以及如何在更大的项目中使用这些插件来增强应用程序的功能。理解这个模式对于构建可扩展和模块化的Java应用至关重要。
总的来说,深入学习`commons-logging-1.0.4`的源码可以增强开发者对日志抽象层的理解,了解如何在Java应用中实现和选择合适的日志框架,并且有助于解决与日志相关的各种问题。通过分析源码,我们可以更好地掌握日志...
这个标签也提醒我们,这个源码包可以用于学习和研究Oracle如何实现JDBC接口,包括连接管理、SQL语句执行、事务控制等核心功能。 在【压缩包子文件的文件名称列表】中: 1. "javax"目录可能包含了Javax.sql和Javax....
通过这个Demo,我们学习了如何利用SPI机制来动态加载和使用服务。这种机制在很多开源框架中都有应用,比如JDBC驱动管理、JNDI服务、JAXB绑定等。SPI使得代码更加模块化,增强了可扩展性,让开发者可以轻松地添加新的...
Java SPI(Service Provider Interface)是Java平台提供的一种服务发现机制,允许JVM在运行时动态加载服务提供...在这个"java spi实现工程"中,我们可以学习如何创建、配置和服务加载,从而更好地理解和应用Java SPI。
本文将深入探讨"plugin管理初稿"这一主题,主要基于提供的`PluginMain.java`文件,结合"源码"和"工具"这两个标签,我们将围绕插件的加载、生命周期管理和API接口设计等方面展开讨论。 首先,`PluginMain.java`通常...
JavaFX 是一个强大的Java图形用户界面(GUI)工具包,用于构建桌面、移动和Web应用程序。这个名为"FXPlugin"的项目显然展示...如果你对JavaFX和插件开发感兴趣,探索"FXPlugin-master"源码将是一个很好的学习实践机会。
6. **SPI 扩展**:Sentinel 利用 Java 的 ServiceLoader 机制实现 SPI(Service Provider Interface),允许用户自定义扩展点。 对于 Windows 和 Linux 客户端,安装和配置过程可能会有所不同,但通常包括以下步骤...
如果遇到问题,可以直接查看源码找出解决方案,或者根据源码理解其工作原理,以便更好地利用库的功能。 此外,还有一个`META-INF`目录,这是Java存档的标准部分,通常包含关于JAR文件的元数据,如清单文件...
- `services`目录:可能包含自定义服务接口的实现类名,供Java的ServiceLoader加载。 **2. WEB-INF** `WEB-INF`目录是Web应用的核心部分,它包含了Web应用的私有资源,对用户直接访问是不可见的。主要包括以下组件...
通过对源码的分析和实践,开发者可以深入了解如何在大型系统中有效地组织和管理服务,提高代码的可扩展性和灵活性。理解并掌握这一机制对于提升Java开发能力,尤其是构建复杂分布式系统的能力,具有重要的意义。
通过查看源码,开发者可以学习到如何利用这个库来构建自己的插件和服务,以及如何与SciJava生态系统中的其他项目进行交互。此外,对于想要贡献于`Scijava Common` 的开发者来说,源码也是理解和改进该项目的基础。 ...
SPI(Service Provider Interface)是Java平台提供的一种服务发现机制,全称为Java...通过对`shican-spi-master`源码的学习和研究,我们可以深入了解如何在Java项目中有效利用SPI技术,实现更加灵活和强大的服务扩展。
通过查看源码,开发者可以深入理解FakeplexCore的工作原理,并学习如何编写和集成自己的插件。此外,文档部分通常会详细介绍FakeplexCore的API、使用方法以及最佳实践,对于开发者来说是宝贵的参考资料。 总之,...