preface
ServiceLoader
isJava
An SPI (Service Provider Interface) framework is provided to decouple Service providers from Service consumers- In this article, I will lead you to understand
ServiceLoader
Principle 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:
-
- Perform database driver class loading (optional) :
Class.forName("com.mysql.jdbc.driver")
- Perform database driver class loading (optional) :
-
- Connect to database:
DriverManager.getConnection(url, user, password)
- Connect to database:
-
- Create SQL statement:
Connection#.creatstatement();
- Create SQL statement:
-
- Execute SQL statement and process result set:
Statement#executeQuery()
- Execute SQL statement and process result set:
-
- Release resources:
ResultSet#close()
,Statement#close()
withConnection#close()
- Release resources:
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 interface
S
, see source code:if (! service.isAssignableFrom(c))
- The service implementation class needs to contain a constructor with no arguments,
ServiceLoader
An 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 required
UTF-8
Code, see source code:new BufferedReader(new InputStreamReader(in, "utf-8"));
- The service implementation class must implement the service interface
-
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
ServiceLoader
There 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
-
- Use class loader to load:
Class<? > c = Class.forName(cn, false, loader);
- Use class loader to load:
The class loader will perform the load -> link, not the initialization
-
- 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
-
ServiceLoader
Based on theSPIThe idea is to decouple service providers from service consumersmodular,componentizationAn implementation of
-
ServiceLoader
Is a relatively simple framework, often only inJava
Used in source code, to meet the needs of complex business, will generally use provideSPI
Third party framework for functionality, such as backendDubbo
, clientARouter
withWMRouter
Etc.
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)