一.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提供了监听机制。还有没有其他原因?