Introduction of SPI implementation principle
SericeLoader
from SPI Practical Learning in JAVA One of the most important ways to implement lookup is to:
ServiceLoader shouts = ServiceLoader.load(Handler.class);
Its class structure is as follows:
public final class ServiceLoader<S> implements Iterable<S> { //SPI Profile All Paths private static final String PREFIX = "META-INF/services/"; // The class or interface representing the service being loaded private final Class<S> service; // The class loader used to locate, load, and instantiate providers private final ClassLoader loader; // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // The current lazy-lookup iterator private LazyIterator lookupIterator;
Load method
A Service Loader is instantiated directly.
return new ServiceLoader<>(service, loader);
In the constructor of ServiceLoader,
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); //Get the class loader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
reload
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
Finding Implementation Classes
The process of finding and creating implementation classes is done in Lazy Iterator. When we call iterator.hasNext and iterator.next methods, we actually call the corresponding methods of Lazy Iterator.
List handlers = Lists.newArrayList(shouts.iterator());
ServiceLoader shouts = ServiceLoader.load(Animal.class);
for (Animal s : shouts) {
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //Configuration File Full Path Name of Splice Interface, Load 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; } //Parsing the content in the configuration file pending = parse(service, configs.nextElement()); } //Get the class name of an implementation class in the configuration file nextName = pending.next(); return true; }
Create examples
When you call the next method, you actually call lookupIterator.nextService. It creates instances of implementation classes by reflection and returns them.
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); //Has been assigned back in the previous step 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 { //Reflect to create instances S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
next---->nextService----->hasNextService---->class.forName