2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
23 import java.security.AccessController;
24 import java.security.PrivilegedAction;
27 * A service-provider loader.
29 * <p>A service provider is a factory for creating all known implementations of a particular
30 * class or interface {@code S}. The known implementations are read from a configuration file in
31 * {@code META-INF/services/}. The file's name should match the class' binary name (such as
32 * {@code java.util.Outer$Inner}).
34 * <p>The file format is as follows.
35 * The file's character encoding must be UTF-8.
36 * Whitespace is ignored, and {@code #} is used to begin a comment that continues to the
38 * Lines that are empty after comment removal and whitespace trimming are ignored.
39 * Otherwise, each line contains the binary name of one implementation class.
40 * Duplicate entries are ignored, but entries are otherwise returned in order (that is, the file
41 * is treated as an ordered set).
43 * <p>Given these classes:
46 * public interface MyService { ... }
47 * public class MyImpl1 implements MyService { ... }
48 * public class MyImpl2 implements MyService { ... }
50 * And this configuration file (stored as {@code META-INF/services/a.b.c.MyService}):
52 * # Known MyService providers.
53 * a.b.c.MyImpl1 # The original implementation for handling "bar"s.
54 * a.b.c.MyImpl2 # A later implementation for "foo"s.
56 * You might use {@code ServiceProvider} something like this:
58 * for (MyService service : ServiceLoader<MyService>.load(MyService.class)) {
59 * if (service.supports(o)) {
60 * return service.handle(o);
65 * <p>Note that each iteration creates new instances of the various service implementations, so
66 * any heavily-used code will likely want to cache the known implementations itself and reuse them.
67 * Note also that the candidate classes are instantiated lazily as you call {@code next} on the
68 * iterator: construction of the iterator itself does not instantiate any of the providers.
70 * @param <S> the service class or interface
73 public final class ServiceLoader<S> implements Iterable<S> {
74 private final Class<S> service;
75 private final ClassLoader classLoader;
76 private final Set<URL> services;
78 private ServiceLoader(Class<S> service, ClassLoader classLoader) {
79 // It makes no sense for service to be null.
80 // classLoader is null if you want the system class loader.
81 if (service == null) {
82 throw new NullPointerException();
84 this.service = service;
85 this.classLoader = classLoader;
86 this.services = new HashSet<URL>();
91 * Invalidates the cache of known service provider class names.
93 public void reload() {
98 * Returns an iterator over all the service providers offered by this service loader.
99 * Note that {@code hasNext} and {@code next} may throw if the configuration is invalid.
101 * <p>Each iterator will return new instances of the classes it iterates over, so callers
102 * may want to cache the results of a single call to this method rather than call it
105 * <p>The returned iterator does not support {@code remove}.
107 public Iterator<S> iterator() {
108 return new ServiceIterator(this);
112 * Constructs a service loader. If {@code classLoader} is null, the system class loader
115 * @param service the service class or interface
116 * @param classLoader the class loader
117 * @return a new ServiceLoader
119 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader classLoader) {
120 if (classLoader == null) {
121 classLoader = ClassLoader.getSystemClassLoader();
123 return new ServiceLoader<S>(service, classLoader);
126 private void internalLoad() {
129 String name = "META-INF/services/" + service.getName();
130 services.addAll(Collections.list(classLoader.getResources(name)));
131 } catch (IOException e) {
137 * Constructs a service loader, using the current thread's context class loader.
139 * @param service the service class or interface
140 * @return a new ServiceLoader
142 public static <S> ServiceLoader<S> load(Class<S> service) {
143 return ServiceLoader.load(service, Thread.currentThread().getContextClassLoader());
147 * Constructs a service loader, using the extension class loader.
149 * @param service the service class or interface
150 * @return a new ServiceLoader
152 public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
153 ClassLoader cl = ClassLoader.getSystemClassLoader();
155 while (cl.getParent() != null) {
159 return ServiceLoader.load(service, cl);
163 * Internal API to support built-in SPIs that check a system property first.
164 * Returns an instance specified by a property with the class' binary name, or null if
165 * no such property is set.
168 public static <S> S loadFromSystemProperty(final Class<S> service) {
169 return AccessController.doPrivileged(new PrivilegedAction<S>() {
172 final String className = System.getProperty(service.getName());
173 if (className != null) {
174 Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
175 return (S) c.newInstance();
178 } catch (Exception e) {
186 public String toString() {
187 return "ServiceLoader for " + service.getName();
190 private class ServiceIterator implements Iterator<S> {
191 private final ClassLoader classLoader;
192 private final Class<S> service;
193 private final Set<URL> services;
195 private boolean isRead = false;
197 private LinkedList<String> queue = new LinkedList<String>();
199 public ServiceIterator(ServiceLoader<S> sl) {
200 this.classLoader = sl.classLoader;
201 this.service = sl.service;
202 this.services = sl.services;
205 public boolean hasNext() {
209 return (queue != null && !queue.isEmpty());
212 @SuppressWarnings("unchecked")
215 throw new NoSuchElementException();
217 String className = queue.remove();
219 return service.cast(classLoader.loadClass(className).newInstance());
220 } catch (Exception e) {
221 throw new ServiceConfigurationError("Couldn't instantiate class " + className, e);
225 private void readClass() {
226 for (URL url : services) {
227 BufferedReader reader = null;
229 reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
231 while ((line = reader.readLine()) != null) {
232 // Strip comments and whitespace...
233 int commentStart = line.indexOf('#');
234 if (commentStart != -1) {
235 line = line.substring(0, commentStart);
238 // Ignore empty lines.
239 if (line.isEmpty()) {
242 String className = line;
243 checkValidJavaClassName(className);
244 if (!queue.contains(className)) {
245 queue.add(className);
249 } catch (Exception e) {
250 throw new ServiceConfigurationError("Couldn't read " + url, e);
253 if (reader != null) {
256 } catch (IOException ex) {
262 public void remove() {
263 throw new UnsupportedOperationException();
266 private void checkValidJavaClassName(String className) {
267 for (int i = 0; i < className.length(); ++i) {
268 char ch = className.charAt(i);
269 if (!Character.isJavaIdentifierPart(ch) && ch != '.') {
270 throw new ServiceConfigurationError("Bad character '" + ch + "' in class name");