preface

  • ServiceLoaderisJavaAn SPI (Service Provider Interface) framework is provided to decouple Service providers from Service consumers
  • In this article, I will lead you to understandServiceLoaderPrinciple and design ideas, hope to help. Please like it, your likes and attention really mean a lot to me!

directory


1. Introduction of SPI

  • define

A service registration and discovery mechanism

  • role

Help achieve modularization and componentization by decoupling service providers from service consumers


2. Procedure for using ServiceLoader

We’ll use the JDBC example directly to help you build a basic understanding of ServiceLoader, as follows:

We all know that there are five basic steps to JDBC programming:

    1. Perform database driver class loading (optional) :Class.forName("com.mysql.jdbc.driver")
    1. Connect to database:DriverManager.getConnection(url, user, password)
    1. Create SQL statement:Connection#.creatstatement();
    1. Execute SQL statement and process result set:Statement#executeQuery()
    1. Release resources:ResultSet#close(),Statement#close()withConnection#close()

Operation database needs to use the database driver provided by the manufacturer, the direct use of the manufacturer’s driver coupling is too strong, the more recommended method is to use DriveManager management class:

Step 1: Define the service interface

JDBC abstracts out a service interface that the database-driven implementation class implements uniformly:

Public interface Driver {// Create a database Connection. Connection connect(String URL, java.util.properties info) throws SQLException; // omit other methods... }Copy the code

Step 2: Implement the service interface

The service provider (database vendor) provides one or more classes (driver implementation classes) that implement this service, as follows:

  • mysql:com.mysql.cj.jdbc.Driver.java
Public class Driver extends NonRegisteringDriver implements java.sql.Driver {static {try {// Registers the Driver java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!" ); }} // omit... }Copy the code
  • oracle:oracle.jdbc.driver.OracleDriver.java
public class OracleDriver implements Driver { private static OracleDriver defaultDriver = null; static { try { if (defaultDriver == null) { //1. Singleton defaultDriver = new OracleDriver(); / / registration drive DriverManager. RegisterDriver (defaultDriver); } } catch (RuntimeException localRuntimeException) { ; } catch (SQLException localSQLException) { ; }} // omit... }Copy the code

Step 3: Register the implementation class to the configuration file

Create a java.sql.Driver configuration file (named as the fully qualified name of the service interface). Each line in the file contains the fully qualified name of the implementation class. For example:

com.mysql.cj.jdbc.Driver
Copy the code

You can unzip the mysql-connector-java-8.0.19.jar package to find the META-INF folder.

Step 4: Load the service

// DriverManager.java static { loadInitialDrivers(); } private static void loadInitialDrivers() {//... The AccessController. DoPrivileged (new PrivilegedAction < Void > () {public Void the run () {/ / use the ServiceLoader traversal implementation class ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadeddrivers.iterator (); Iterator<Driver> driversIterator = loadeddrivers.iterator (); // try{while(driversiterator.hasnext ()) {driversiterator.next (); // Query: why no processing? } } catch(Throwable t) { // Do nothing } return null; }}); // omit minor code... }Copy the code

As you can see, the static block of DriverManager calls loadInitialDrivers (). Inside the method, the ServiceLoader Iterator

iterates through all the Driver implementation classes, but why is there no action in the iteration?

while(driversIterator.hasNext()) { driversIterator.next(); // Query: why no processing? }Copy the code

In the next section, we dig into the ServiceLoader source code to answer this question.


3. ServiceLoader source code analysis

# hint #

Some of ServiceLoader source USES the safety inspection, such as the AccessController. The doPrivileged (), the omitted in the following code

  • The factory method

ServiceLoader provides three static generic factory methods that will eventually call serviceloader.load (Class,ClassLoader) internally, as follows:

Public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {// Use the parent delegate model of the top ClassLoader ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl ! = null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } // 2. Public static <S> ServiceLoader<S> load(Class<S> service) Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } // 3. public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); }Copy the code

As you can see, the three methods in the incoming only this parameters have the difference, if you don’t understand this, please be sure to read [the Java | take you understand this principle and design idea of “] (Editting…).

  • A constructor
private final Class<S> service;
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

private ServiceLoader(Class<S> svc, ClassLoader cl) { 
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    reload();
}

public void reload() {
    // 清空 providers
    providers.clear();
    // 实例化 LazyIterator
    lookupIterator = new LazyIterator(service, loader);
}
Copy the code

As you can see, the ServiceLoader constructor creates an instance of the LazyIterator, which is a “lazy-loaded” iterator. So where is this iterator used? Keep reading

  • External iterator
private LazyIterator lookupIterator; // Return a new 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

Iterator Iterator () is a generic method that wraps the providers set Iterator and lookupIterator, getting the elements from providers first.

Why get elements from the providers collection first? LazyIterator# Next () puts the elements of each iteration into the providers collection, which is the LazyIterator’s in-memory cache.

  • Inner iterator
# hint #

Try-catch is omitted from the source code in the following code summary

// ServiceLoader.java private static final String PREFIX = "META-INF/services/"; private class LazyIteratorimplements 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; } private boolean hasNextService() { if (nextName ! = null) { return true; } if (configs == null) {// Configs is not initialized. META-INF/services/ service interface fully qualified name String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } while ((pending == null) || ! pending.hasNext()) { if (! configs.hasMoreElements()) { return false; } // Point 1: Parse (service, configs.nextelement ()); } // nextName: the fully qualified name of the next implementation class nextName = pending.next(); return true; } private S nextService() { if (! hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; // 1. Use Class loader to load Class<? > c = Class.forName(cn, false, loader); if (! service.isAssignableFrom(c)) { ClassCastException cce = new ClassCastException(service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); fail(service, "Provider " + cn + " not a subtype", cce); P = service.cast(c.newinstance ()); Providers. Put (cn, p); return p; } public boolean hasNext() { return hasNextService(); } public S next() { return nextService(); } public void remove() { throw new UnsupportedOperationException(); Private Iterator<String> parse(Class<? > service, the URL u) throws ServiceConfigurationError {/ / use utf-8 encoding input configuration file resources InputStream in = u.o. penStream (); BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8")); ArrayList<String> names = new ArrayList<>(); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); return names.iterator(); }Copy the code

4. The ServiceLoader points

With the ServiceLoader source code in mind, the main points are as follows:

  • The constraint

    • The service implementation class must implement the service interfaceS, see source code:if (! service.isAssignableFrom(c))
    • The service implementation class needs to contain a constructor with no arguments,ServiceLoaderAn instance of the service implementer will be created using this no-argument constructor, see source code:S p = service.cast(c.newInstance());
    • The configuration file is requiredUTF-8Code, see source code:new BufferedReader(new InputStreamReader(in, "utf-8"));
  • Lazy loading

The ServiceLoader uses a “lazy load” approach to creating service implementation class instances, which are created only when the iterator is advancing. See nextService()

  • Memory cache

The ServiceLoader creates an instance of the service implementation class using the LinkedHashMap cache, which is traversed in Map#put execution order during the second iteration

  • Choice of service implementation

When there are multiple providers, the service consumer module does not have to use all of them, but rather needs to filter the best implementation based on certain features. ServiceLoader’s mechanism can only determine the best implementation from the found implementation class as it iterates through the iterator, for example using charset.forname (String) to get the Charset implementation class:

Public abstract Class CharsetProvider {public Abstract Charset charsetForName(String charsetName); // omit other methods... } // charset.java public static Charset forName(String charsetName) {// ServiceLoader<CharsetProvider> sl = ServiceLoader.load(CharsetProvider.class, cl); Iterator<CharsetProvider> i = sl.iterator(); for (Iterator<CharsetProvider> i = providers(); i.hasNext();) { CharsetProvider cp = i.next(); Return Charset cs = cp. CharsetForName (charsetName); if (cs ! = null) return cs; }}Copy the code
  • ServiceLoaderThere is no mechanism to log out of the service

Once an instance of a service implementation class is created, its garbage collection behaves like any other object in Java, only if the object does not have a strong reference to GC Root.


5. Problem regression

Now we return to the question posed by reading the DriverManager source code:

while(driversIterator.hasNext()) { driversIterator.next(); // Query: why no processing? }Copy the code

Why does the next() operation neither get the service implementation class object nor do any subsequent processing? LazyIterator#next()

NextService () private S nextService() {if (! hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; // 1. Use Class loader to load Class<? > c = Class.forName(cn, false, loader); if (! service.isAssignableFrom(c)) { ClassCastException cce = new ClassCastException(service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); fail(service, "Provider " + cn + " not a subtype", cce); P = service.cast(c.newinstance ()); Providers. Put (cn, p); return p; }Copy the code
    1. Use class loader to load:Class<? > c = Class.forName(cn, false, loader);

The class loader will perform the load -> link, not the initialization

    1. Instantiate the service implementation Class according to Class

Since class loading is guaranteed before creating an instance of the class, initialization is implicitly performed by the classloader, which includes the static code block execution of the class

Looking back com. Mysql. Cj.. JDBC Driver and oracle. JDBC Driver. OracleDriver source, we have found similar static block of code:

The static {try {/ / registered drive Java SQL. DriverManager. RegisterDriver (new Driver ()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!" ); }}Copy the code

As you can see, they both call DriverManager#registerDriver to register an instance of the service implementation class, which is stored in CopyOnWriteArrayList, from which the database driver is retrieved when the database connection is obtained. Now, do you understand?


6. Summary

    1. ServiceLoaderBased on theSPIThe idea is to decouple service providers from service consumersmodular,componentizationAn implementation of
    1. ServiceLoaderIs a relatively simple framework, often only inJavaUsed in source code, to meet the needs of complex business, will generally use provideSPIThird party framework for functionality, such as backendDubbo, clientARouterwithWMRouterEtc.

In future articles, I will discuss the source implementation of ARouter and WMRouter. Please follow Peng’s blog.


The resources

  • WMRouter lot – at meituan
  • ARouter lot – alibaba
  • ServiceLoader – Android Developers
  • Meituan cat’s eye movie android modular combat — probably the most detailed modular combat — by Chen wenchao happylion
  • WMRouter: Meituan Takeaway Android Open Source Routing Framework by Zi Jian Erudao Yunchi (Meituan technical team)
  • Android Componentize Scheme and Modular Message Bus Modular Event Combat by Hai Liang (Meituan technology team)

Recommended reading

  • Android | show you understand NativeAllocationRegistry principle and design idea
  • Android | article bring you a comprehensive understanding of AspectJ framework
  • Android | use AspectJ limit button click quickly
  • Android | this is a detailed EventBus use tutorial
  • Developers | five kinds of forms of social sharing App is analysed
  • Developers | those a “crash” UI acceptance
  • Computer composition principle | Unicode utf-8 and what is the relationship?
  • | why floating-point arithmetic of computer constitute principle is not accurate? (Ali written test)

Thank you! Your “like” is the biggest encouragement for me! Welcome to attentionPeng XuruiThe lot!