SPI- Service discovery mechanism

A thought triggered by a piece of JDBC code

Here is a very common JDBC usage code:

@Test
public void testJDBC(a) throws SQLException, ClassNotFoundException {
    String url = "jdbc:mysql://localhost:3307/mls";
    String userName = "root";
    String password = "123456";
    // 1 Load the driver
    // Class.forName("com.mysql.cj.jdbc.Driver"); 
    // 2 Create a connection
    Connection con = DriverManager.getConnection(url, userName, password);
    // 3 Create statement
    Statement statement = con.createStatement();
    String sql = "select * from mlsdb where id=1";
    // 4 Run the SQL command to obtain the result
    ResultSet rs = statement.executeQuery(sql);
    while (rs.next()) {
        System.out.println(rs.getString("province")); }}Copy the code

Among them the first Class. Class.forname (“. Com. Mysql. Cj. JDBC Driver “) through the fully qualified Class name com. Mysql. Cj). The JDBC Driver, triggered the Class loading process, and com. Mysql. Cj). The JDBC Driver Class source code is as follows:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver(a) throws SQLException {}static {
        try {
            // Register the mySql driver with DriverManager
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!"); }}}Copy the code

You can see that the class contains a static block of code, static {}, which is collected by the compiler in the

() method of the class file and executed during the initialization phase of the class load. The result is to register the mysql driver with DriverManager

Careful readers will notice class.forname (” com.mysql.cj.jdbc.driver “); This line is commented out. Does that mean this code is optional? If not, how does DriverManager use mysql drivers to create connections? When did mysql drivers get secretly registered to DriverManager? With these questions in mind, let’s analyze the main character of this article –SPI(Service Provider Interface Service discovery Mechanism) bit by bit through code.

Source code analysis

DriverManager is the basic service class used to manage JDBC drivers. It is in the java.sql package and is therefore loaded by the Bootstrap ClassLoader. When the class is loaded, the following code block is executed:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
Copy the code

The static code block above executes the loadInitialDrivers() method, which is used to load individual database drivers. The code is as follows:

    private static void loadInitialDrivers(a) {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run(a) {
                    return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
            drivers = null;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run(a) {
                // Instantiate the ServiceLoader object and inject the thread context classloader and driver.class
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                // Get the iterator
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(/* Find the class that implements the Driver interface */driversIterator.hasNext()) {
                        // Perform class loadingdriversIterator.next(); }}catch(Throwable t) {
                // Do nothing
                }
                return null; }}); println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: "+ ex); }}}Copy the code
  • ServiceLoader.load(Driver.class)This method instantiates a ServiceLoader object and injects it with the thread context classloader and driver.class;
  • loadedDrivers.iterator(): Get the iterator of the ServiceLoader object;
  • driversIterator.hasNext(): Find the Driver class;
  • driversIterator.next(): Performs class loading in the implemented “next()” method, using the thread context classloader above.

ServiceLoader.load(Driver.class)

The related call method is as follows:

    // Create a ServiceLoader of type Service using the ClassLoader of the current thread context
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

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

    // 1 Clear the cache
    // 2 Create a lazy-loaded Iterator, and subsequent calls to the hasNext() and next() methods of that Iterator will trigger the actual lookup and instantiation of the services provided by ServiceProvider
    public void reload(a) {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
Copy the code

loadedDrivers.iterator()

The relevant methods are as follows:

    public Iterator<S> iterator(a) {
        return new Iterator<S>() {
            Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
            public boolean hasNext(a) {
                if (knownProviders.hasNext()) return true;
                / / discovery service (LazyIterator hasNextService ())
                return lookupIterator.hasNext();
            }
            public S next(a) {
                if (knownProviders.hasNext()) return knownProviders.next().getValue();
                // Load the service class (lazyiterator.nextService ())
                return lookupIterator.next();
            }
            public void remove(a) {
                throw newUnsupportedOperationException(); }}; }Copy the code

LazyIterator– Lazy loading of iterators

This class is the inner class of the ServiceLoader

     // The service provider needs to save the configuration file to the specified directory
     private static final String PREFIX = "META-INF/services/";	

     // Lazy loaded inner classes for finding and instantiating services provided by ServiceProvider
     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;
        }
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
        
        // Used to find the service
        private boolean hasNextService(a) {
            if(nextName ! =null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // Perform a service lookup
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x); }}while ((pending == null) | |! pending.hasNext()) {if(! configs.hasMoreElements()) {return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
        
        // Used to load the service
        private S nextService(a) {
            if(! hasNextService())throw new NoSuchElementException();
            String cn = nextName;
            nextName = null; Class<? > c =null;
            try {
                // Use loader for class loading
                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());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service, "Provider " + cn + " could not be instantiated", x);
            }
            throw new Error();          // This cannot happen}}Copy the code

After going through the above operations in the ServiceLoader class (including service lookup and class loading), the Driver class in the mysql Driver package is initialized. The class is shown below

The class is defined as follows:

The static code block is executed during the instantiation phase of the com.mysql.cj.jdbc.driver class load to register the Driver with DriverManager.

conclusion

inJDK6A new feature has been introducedServiceLoaderAccording to the official documentation, it is mainly used to load a series ofservice provider. andServiceLoaderCan be achieved byservice providerTo load the specified configuration fileservice provider. When the service provider provides an implementation of the service interface, we only need to implement it in the JAR packageMETA-INF/services/Create a file named after the service interface in the directory. This file contains the concrete implementation class that implements the service interface. When an external application assembs the module, it can find the implementation class name in the meta-INF /services/ jar file and load the instantiation to complete the module injection.