Java 6 introduced a feature for discovering and loading implementations that match a given interface: the Service Provider Interface (SPI).
SPI is a mechanism provided by Java to discover and load implementations that match a given interface. It is mainly used to extend existing system applications, among other things. There are many applications in the JDK, such as:
java.nio.file.spi.FileSystemProvider

Using SPI
Define the interface and implementation
/** * @author xwolf */public interface IEat {
void eat();
}
public class Apple implements IEat {
@Override public void eat() { System.out.println("eat Apple...."); }}
public class Peach implements IEat {
@Override public void eat() { System.out.println("eat Peach..."); }}
public class Pear implements IEat {
@Override public void eat() { System.out.println("eat Pear..."); }}
Then create a folder specified by SPI in the project resource directory: META-INF/services/, and finally create a descriptor file for SPI, specifying the implementation class information of the interface. The file name must be the fully qualified name of the interface. The file name created in this article is com.xwolf.java.spi.IEat, and the content consists of specific implementation classes, which can be separated by lines.
com.xwolf.java.spi.Apple
com.xwolf.java.spi.Pear
Finally, define the specific implementation that calls the service:
import java.util.ServiceLoader;
/** * @author xwolf */public class EatProvider {
public static void main(String[] args) { ServiceLoader<IEat> eatProvider = ServiceLoader.load(IEat.class); for (IEat eat : eatProvider){ eat.eat(); } }}
You will see that the corresponding methods can be called normally.

ServiceLoader Source Code
public final class ServiceLoader<S> implements Iterable<S> {
// Directory prefix private static final String PREFIX = "META-INF/services/";
// Information of the loaded interface or class private final Class<S> service;
// Class loader type for instantiating classes private final ClassLoader loader;
// Permission control context private final AccessControlContext acc;
// Provider information private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// Lazy iterator private LazyIterator lookupIterator;
/** * Clear providers and initialize lazy iterator */ public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
/** Private constructor */ private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); // If the class loader is null, use the system class loader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; // Context control acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); }
// Defined configuration error information private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg); }
private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ":" + line + ": " + msg); }
// Read single line information 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; } // Extract class information before # int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { // Configuration syntax error, cannot contain spaces or escape characters 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); } // Add provider to the list if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } // Line number +1 return lc + 1; }
// Parse configuration file from absolute path on disk to get provider information 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; // Read file content 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(); }
// Lazy iterator private class LazyIterator implements Iterator<S> {
Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; }
// Check if there are services private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // Configuration information 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; } // Parse configuration file information pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
// Get the next service private S nextService() { if (!hasNextService()) 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()); // Add provider information to the map providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } }
public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
public void remove() { throw new UnsupportedOperationException(); }
}
/** * Actual iterator method called, calls the method of the lazy 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; // Call the method of the lazy iterator return lookupIterator.hasNext(); }
public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); }
public void remove() { throw new UnsupportedOperationException(); }
}; }
/** Returns a new ServiceLoader object with the given service and loader */ public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
/** * Returns a new ServiceLoader with the given class and the context class loader of the current thread */ public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
/** * Load the given service with the extension 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); }
/** * toString */ public String toString() { return "java.util.ServiceLoader[" + service.getName() + "]"; }
}
The source code is not much to say, and it is relatively simple. It involves class loading issues; if you want to delve deeper, you can refer to related materials.