What is?

Service Provider Interface (SPI) is a built-in Service discovery mechanism in JDK.

How does it work?

Define Animal interface:

public interface Animal {
    void sayHello();
}
Copy the code

Two implementation classes:

Public implements Animal{@override public void sayHello() {system.out.println (" implements Animal "); }}Copy the code
Public class Cat implements Animal {@override public void sayHello() {system.out.println (" Cat Cat Cat "); }}Copy the code

Put the configuration file and add the configuration:Call:

public class Main { public static void main(String[] args) { ServiceLoader<Animal> load = ServiceLoader.load(Animal.class); Iterator<Animal> iterator = load.iterator(); while (iterator.hasNext()){ iterator.next().sayHello(); }}}Copy the code

What’s the use?

There are many application scenarios for the SPI extension mechanism, such as common-logging, JDBC, Dubbo, and more. SPI process:

  • Interface standards for organization and formula definition
  • Implement meta-INF /services/${interface_name} file

For example, in JDBC scenarios:

  • First of all, the interface java.sql.Driver is defined in Java, and there is no specific implementation, the specific implementation is provided by different vendors. Jars in MySQL MySQL connector – Java – 6.0.6. Jar, you can find the meta-inf/services directory, this directory will have a name for the Java SQL. The Driver file, The file content is com.mysql.cj.jdbc.driver, which is the implementation of the interface defined in Java. The PostgreSQL jar package postgresql-42.0.0. jar contains the same configuration file org.postgresql.Driver, which is PostgreSQL’s implementation of Java’s java.sql.Driver.

Source code analysis

// ServiceLoader implements the Iterable interface Public final class ServiceLoader<S> implements Iterable<S> {// Search for the directory of the configuration file private static final String PREFIX = "META-INF/services/"; Private final Class<S> service; // this ClassLoader is used to locate, load, and instantiate service providers. Private Final AccessControlContext ACC; Private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // private LazyIterator lookupIterator; . Private class LazyIterator implements Iterator<S> {class <S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; 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); Return nextService(); return nextService(); return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); }}; return AccessController.doPrivileged(action, acc); } } private boolean hasNextService() { if (nextName ! = null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); If (loader = = null) / / bring all files loaded in configs. = this getSystemResources (fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); }} / / you can see is an analytical while a directory ((pending = = null) | |! pending.hasNext()) { if (! configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } 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 {// instantiate 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 } } 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; 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(); } 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 (knownprovider.hasnext ()) return knownprovider.next ().getValue();} public S next() {if (knownprovider.hasnext ()) return knownprovider.next ().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); }}; }}Copy the code
  • 1.ServiceLoader implements the Iterable interface, so it has iterator properties. The main ones here are the hasNext and Next methods that implement iterators. LazyIterator provides a lazy implementation of the iterator.
  • The static PREFIX is the “meta-INF /services/” directory. This is why you need to create a file named after the service interface in the meta-INF /services/ directory on your CLASspath.
  • 3. Load the Class object using reflection class.forname (), instantiate the Class using newInstance, cache the instantiated Class into providers objects (LinkedHashMap

    ), and return the instance object.
    ,s>

disadvantages

  • 1. Instead of loading on demand, we need to go through all the implementations, instantiate them, and then find the implementation we need in a loop. If you don’t want to use some implementation class, or if some class is time consuming to instantiate, it is also loaded and instantiated, which is wasteful.
  • 2. The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator.
  • 3. It is not safe for multiple concurrent threads to use instances of the ServiceLoader class.