How does Java implement its own SPI mechanism?JDK Source

Note: The source analysis corresponds to JDK version 1.8

1 Introduction

This is the first article on JDK Source Interpretation in Source Notes. In this article, we will explore the source code related to Java's SPI mechanism.

2 What is SPI mechanism

So, what is the SPI mechanism?

SPI is short for Service Provider Interface, which means Service Provider Interface.We may be a little confused by the literal meaning, but SPI is simply an extension mechanism. We define the implementation class of an interface in the corresponding configuration file, then load and instantiate this instance class in the configuration file based on that interface. In fact, SPI is such a thing.When it comes to SPI mechanisms, the most common is Java's SPI mechanisms, along with Dubbo and SpringBoot custom SPI mechanisms.

With the SPI mechanism, it is possible to flexibly extend some frameworks without writing some of their implementation classes to code.

So how can some frameworks be flexibly extended using SPI mechanisms?Here are a few chestnuts to illustrate:

  1. JDBC Driven Load Case: Using Java's SPI mechanism, we can introduce different JDBC driver packages according to different database vendors.
  2. SPI mechanism for SpringBoot: We can add our custom auto-configuration classes, event listeners, initializers, etc. to spring.factories;
  3. Dubbo's SPI mechanism: Dubbo is the most comprehensive application of SPI mechanism. Dubbo basically provides expansion points for each of its own function points, such as cluster extension, routing extension and load balancing extension, which are close to 30 expansion points.If one of Dubbo's built-in implementations does not meet our needs, we can simply replace our implementation with that of Dubbo using its SPI mechanism.

First of all, the three chestnuts above give us an intuitive idea of how some frameworks can be flexibly extended using the SPI mechanism.

3How do I use the Java SPI?

Let's first see how to use the SPI that comes with Java. Define a Developer interface first

// Developer.java

package com.ymbj.spi;

public interface Developer {
    void sayHi();
}

Then define two implementation classes for the two Developer interfaces:

// JavaDeveloper.java

package com.ymbj.spi;

public class JavaDeveloper implements Developer {

    @Override
    public void sayHi() {
        System.out.println("Hi, I am a Java Developer.");
    }
}
// PythonDeveloper.java

package com.ymbj.spi;

public class PythonDeveloper implements Developer {

    @Override
    public void sayHi() {
        System.out.println("Hi, I am a Python Developer.");
    }
}

Then create a new META-INF/services folder in the project resources directory, and then create a new file named after the fully qualified name of the Developer interface with the following contents:

// com.ymbj.spi.Developer file

com.ymbj.spi.JavaDeveloper
com.ymbj.spi.PythonDeveloper

Finally, we create a new test class, JdkSPITest:

// JdkSPITest.java

public class JdkSPITest {

    @Test
    public void testSayHi() throws Exception {
        ServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);
        serviceLoader.forEach(Developer::sayHi);
    }
}

Run the test class above and the results are as follows:

From the simple Demo above, we know how to use Java's SPI mechanism for extension point loading. Here's a recommended article JAVA Gathering--About SPI Mechanism Through this article, I believe you will have a deep understanding of the Java SPI, especially in the area of JDBC load drivers.

Source Code Interpretation of 4 Java's SPI Mechanism

From the simple Demo that extended the Developer interface earlier, we can see that the implementation of the SPI mechanism for Java is related to the ServiceLoader class, so let's first look at the class structure code for ServiceLoader:

// ServiceLoader implements the [Iterable] interface
public final class ServiceLoader<S>
    implements Iterable<S>{
    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;
    // Construction method
    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();
    }
	
    // ... temporarily omit the relevant code
    
    // ServiceLoader's internal class, LazyIterator, implements the [Iterator] interface
    // Private inner class implementing fully-lazy provider lookup
    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;
        }
        // Override the hasNext method of the Iterator interface
        public boolean hasNext() {
            // ... temporarily omit the relevant code
        }
        // Override the next method of the Iterator interface
        public S next() {
            // ... temporarily omit the relevant code
        }
        // Override the remove method of the Iterator interface
        public void remove() {
            // ... temporarily omit the relevant code
        }

    }

    // Overrides the iterator method of the Iterable interface and returns an iterator
    public Iterator<S> iterator() {
        // ... temporarily omit the relevant code
    }

    // ... temporarily omit the relevant code

}

As you can see, ServiceLoader implements the Iterable interface, overriding its iterator method yields an iterator, while ServiceLoader has an internal class, LazyIterator, and LazyIterator implements the Iterator interface, indicating that LazyIterator is an iterator.

4.1 ServiceLoader.load method, which prepares to load service provider implementation classes

So let's now start exploring the source code for Java's SPI mechanism. First let's look at the first sentence of JdkSPITest code ServiceLoader <Developer> serviceLoader = ServiceLoader.load(Developer.class); ServiceLoader.load(Developer.class); source code:

// ServiceLoader.java

public static <S> ServiceLoader<S> load(Class<S> service) {
    //Gets the current thread context class loader 
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Pass in the service interface class and the thread context class loader as parameters to continue calling the load method
    return ServiceLoader.load(service, cl);
}

Let's look at ServiceLoader.load(service, cl); method:

// ServiceLoader.java

public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    // A new ServiceLoader object was created using the service interface class and the thread context class loader as construction parameters
    return new ServiceLoader<>(service, loader);
}

Continue to look at the new ServiceLoader <> (service, loader); how was it built?

// ServiceLoader.java

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();
}

You can see that when you build a ServiceLoader object, you invoke the reload method in addition to assigning values to its member properties:

// ServiceLoader.java

public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

You can see that a new LazyIterator object is created in the reload method and assigned to lookupIterator.

// ServiceLoader$LazyIterator.java

private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}

You can see that when you build a LazyIterator object, you only assign values to its member variables service and loader attributes. We follow along all the way, and we don't see the implementation classes that load the Developer interface into the META-INF/services folder!It's strange that we're all deceived by the load method name of ServiceLoader.

Remember creating a new LazyIterator object while parsing the previous code?Lazy means lazy, Iterator means iteration.We guess at this point that the purpose of the LazyIterator object should be to load the implementation class of the Developer interface during iteration.

4.2 ServiceLoader.iterator method to implement lazy loading of classes by service providers

Now let's look at the second sentence of JdkSPITest, serviceLoader.forEach(Developer::sayHi); after executing this code, the iterator method of serviceLoader is finally called:

// serviceLoader.java

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;
            // Call the hasNext method of lookupIterator, LazyIterator
            // You can see that the hasNext method delegated to LazyIterator is implemented
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            // Call lookupIterator, the next method of LazyIterator
            // You can see that the next method delegated to LazyIterator is implemented
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

You can see that calling the iterator method of serviceLoader returns an anonymous iterator object, which is essentially equivalent to a faceface class, whose overrides the hasNext and next methods delegate the hasNext and next methods of LazyIterator, respectively.

As we continue debugging, we find that we will enter the hasNext method of LazyIterator next:

// serviceLoader$LazyIterator.java

public boolean hasNext() {
    if (acc == null) {
        // Call hasNextService method
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

Continue to follow the hasNextService method:

// serviceLoader$LazyIterator.java

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            // PREFIX = "META-INF/services/"
            // service.getName() is the fully qualified name of the interface
            // Remember that the previous code built the LazyIterator object by assigning its member property service
            String fullName = PREFIX + service.getName();
            // Load the service provider class in the interface file under META-INF/services/directory
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                // Remember that the previous code built the LazyIterator object by assigning its member property loader
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        // Returns the service provider class in the interface file under META-INF/services/directory and assigns it to the pending property
        pending = parse(service, configs.nextElement());
    }
    // Then take out a fully qualified name and assign it to the member variable nextName of LazyIterator
    nextName = pending.next();
    return true;
}

You can see that when the hasNextService method of LazyIterator is executed, the contents of the interface file are ultimately loaded into the META-INF/services/directory, that is, the fully qualified name of the service provider implementation class is loaded, and a service provider implementation class is taken out and assigned to the member variable nextName of LazyIterator.When we get here, we understand that LazyIterator is really lazy to load until it is used.

Think: Why lazy loading here?What is the idea of lazy loading?What's the advantage of lazy loading?Can you name other cases of lazy loading?

Similarly, after executing the hasNext method of LazyIterator, the next method of LazyIterator will continue to be executed:

// serviceLoader$LazyIterator.java

public S next() {
    if (acc == null) {
        // Call the nextService method
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

We continue to follow up on the nextService method:

// serviceLoader$LazyIterator.java

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // Remember to assign the fully qualified name of the service provider implementation class to nextName in the hasNextService method
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        // [1] Load the service provider implementation class in classpath based on the fully qualified name of the incoming class loader and service provider implementation class
        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 {
        // [2] Instantiate and transform the service provider implementation class just loaded
        S p = service.cast(c.newInstance());
        // [3] Eventually put the instantiated service provider implementation class into the providers collection
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

You can see that the nextService method of the LazyIterator ultimately instantiates the service provider implementation class that was loaded before, puts it into the providers collection, and then calls the service provider implementation class method (for example, the JavaDeveloper sayHi method here).Note that once a service provider implementation class is loaded, if there is a method in the main function that calls the service provider implementation class, its method is called immediately; then the next service provider class is instantiated.

Design mode: You can see that the SPI mechanism in Java implements code that uses the iterator mode, which shields the internal structure differences of various storage objects and provides a unified view to traverse each storage object (storage objects can be collections, arrays, etc.).java.util.Iterator is also an implementation of the iterator pattern: at the same time, each collection class in Java generally implements the Iterable interface, implements its iterator method to obtain the implementation class object of the Iterator interface (generally an internal class of the collection), and then uses the hasNext and next methods of the Iterator object's implementation class to iterate through the collection elements.

Interpretation of 5 JDBC Driver Loading Source

The source code implementation of Java's SPI mechanism has been analyzed before. Now let's look at the actual application of Java's SPI mechanism.

As we all know, JDBC-driven loading is a typical application case of Java's SPI mechanism.JDBC mainly provides a set of interface specifications, and the api of these specifications is implemented in the core library of Java (rt.jar). Different database vendors can connect to databases in Java language by writing driver code that conforms to this set of JDBC interface specifications.

The core interfaces and classes in java's core library (rt.jar) that are loaded with JDBC drivers are the java.sql.Driver interface and the java.sql.DriverManager class, where java.sql.Driver is the interface implemented by the driver classes of various database vendors, while DriverManager is the driver class used to manage databases. It is worth noting that DriverManager has a registeredDrivers collection genusSex, the driver class used to store the database.

// DriverManager.java

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

This is an example of loading a Mysql driver to analyze the source code for the JDBC driver load.

After our project introduced the mysql-connector-java dependency (version 5.1.47 here), Mysql's driver implementation class file looks like the following:

You can see that there are two Driver driver classes in Mysql's driver package, com.mysql.jdbc.Driver and com.mysql.fabric.jdbc.FabricMySQLDriver, which are generally only used by default.

5.1 Loading Mysql's driver classes using Java's SPI

Now let's explore how the code for JDBC driver loading is implemented.

Let's start with a simple JDBC test code:

// JdbcTest.java

public class JdbcTest {
    public static void main(String[] args) {
        Connection connection = null;  
        Statement statement = null;
        ResultSet rs = null;

        try {
            // Note: In the JDBC 4.0 specification, there is no need to write code that explicitly loads the database as before
            // Class.forName("com.mysql.jdbc.Driver");
            // Get the database connection, note [drive packages for mysql will be loaded here]
            /***************[Main Line, Start Point **********/
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456");
            // Create Statement Statement Statement
            statement = connection.createStatement();
            // Execute Query Statement
            rs = statement.executeQuery("select * from user");
            // Traverse query result set
            while(rs.next()){
                String name = rs.getString("name");
                System.out.println(name);
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            // ...omit the code to release the resource
        }
    }
}

When the main function of JdbcTest calls the getConnection method of DriverManager, the code of the static code block of the DriverManager class must be executed first, then the getConnection method, so first look at the static code block of DriverManager:

// DriverManager.java

static {
    // Load Driven Implementation Class
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

Continue to follow the code for loadInitialDrivers:

// DriverManager.java

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // Does it feel like we've known each other since we got here? Yes, we executed the following two sentences in the previous JdkSPITest code
            // This code has been parsed before and does not actually load the service provider implementation class
            // Instead, instantiate a ServiceLoader object and instantiate a LazyIterator object for lazy loading
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // Calling the iterator method of ServiceLoader loads and instantiates the META-INF/services/java.sql.Driver file while iterating
            // Two driver classes are com.mysql.jdbc.Driver and com.mysql.fabric.jdbc.FabricMySQLDriver for
            /****************[Main Line, Focus on ********************/
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.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);
        }
    }
}

In the code above, we can see that Mysql's driver class loading is mainly achieved by using Java's SPI mechanism, that is, by using ServiceLoader to load and instantiate Mysql's driver class.

5.2 Register Mysql's driver class

So, the above code is just loading and instantiating the Mysql driver class, so how is the driver class registered with the registeredDrivers collection of DriverManager?

At this point, we notice that there is also a static block of code in the com.mysql.jdbc.Driver class, that is, instantiating the class will certainly trigger the execution of the static block code, so let's look directly at what this static block does:

// com.mysql.jdbc.Driver.java

// Register ourselves with the DriverManager
static {
    try {
        // Register yourself in the registeredDrivers collection of the DriverManager class
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

You can see that the original Mysql driver class, com.mysql.jdbc.Driver, registers itself in the registeredDrivers collection of DriverManager when instantiating and when executing its static code block.

Okay, continue to follow DriverManager's registerDriver method:

// DriverManager.java

public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
    // Continue calling the registerDriver method
    registerDriver(driver, null);
}

public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        // Register driver driver class instances into the registeredDrivers collection
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);
}

By analyzing this, we understand how Java's SPI mechanism loads Mysql's driver classes and registers Mysql's driver classes in DriverManager's registeredDrivers collection.

5.3 Connect to the database using a previously registered Mysql driver class

Now that Mysql's driver class has been registered, when will it be used?

We're going to connect to the Mysql database, so naturally we need the Mysql driver class, right?Here we go back to JDBC's test code JdbcTest class connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "123456"); in this code, take a look at the source code for getConnection:

// DriverManager.java

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }
    // Continue calling the getConnection method to connect to the database
    return (getConnection(url, info, Reflection.getCallerClass()));
}

Continue to follow up on the getConnection method:

// DriverManager.java

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
        println("DriverManager.getConnection(\"" + url + "\")");
        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;
        // Traverse through the registeredDrivers collection, noting that previously loaded instances of the Mysql driver class are registered with the collection
        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            // Determine whether you have permission
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    // Connect to the database using the Mysql driver class
                    /*************[Main Line, Focus on *******************/
                    Connection con = aDriver.driver.connect(url, info);
                    // As long as the connection is connected, the remaining driver classes loaded, such as FabricMySQLDriver, will be ignored because they are returned directly below
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

You can see that the DriverManager getConnection method pulls the Mysql driver class just loaded from the registeredDrivers collection to connect to the database.

Okay, here, the source code for the JDBC driver load is basically analyzed.

6-Thread Context Class Loader

The source code for JDBC driver loading has been basically analyzed, but one important point that has not been explained is the thread context class loader, which breaks the parental delegation model of the class loading mechanism.

As we all know, the related classes of the JDBC specification (such as the previous java.sql.Driver and java.sql.DriverManager) are under the rt.jar package of Jdk, which means that these classes will be loaded by the BootstrapClassLoader; while the driver classes of Mysql are implemented by external database vendors and are also in the classpath of the project when they are introduced.It is definitely impossible to load these driver classes by starting the class loader at this time. What should I do now?

Due to the drawbacks of the parent delegation model of the class loading mechanism, the parent delegation model can only be broken.Since the classes in the project classpath are loaded by the Application Class Loader, can we "reverse" the launch class loader to delegate the application class loader to load the driver classes from these external database vendors?If so, how do we get the startup class loader to delegate the application class loader to load What about classes in classpath?

The answer is yes, we can set the application class loader inside the thread, that is, the contextClassLoader, which newly defines a class loader property inside the thread, and then set the application class loader into the contextClassLoader property of the thread at some point in time. If not, the default is the application class loader.When the class loader is then started to load classes such as java.sql.Driver and java.sql.DriverManager, the contextClassLoader, the application class loader, is also removed from the current thread to load JDBC driver classes provided by external vendors in the classpath.Therefore, by breaking the parental delegation model of the class loading mechanism, using the thread context class loader perfectly solves this problem.

Now let's go back and see when the thread context class loader was acquired when the Mysql driver was loaded.

The answer is that the loadInitialDrivers method of DriverManager calls ServiceLoader <Driver> loadedDrivers = ServiceLoader.load (Driver.class); this code, and removing the thread context class loader is taken out in the load method of ServiceLoader:


public static <S> ServiceLoader<S> load(Class<S> service) {
    // Remove Thread Context Class Loader Removes contextClassLoader, while contextClassLoader loads application class loader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Pass in the thread context class loader just taken as a parameter to load the driver class provided by the external vendor in the classpath
    return ServiceLoader.load(service, cl);
}

Therefore, here we understand the role that the thread context class loader plays in loading JDBC driver packages.In addition, we should know that most of the SPI-related loading in Java is done using thread context class loaders, such as JNDI,JCE,JBI, and so on.

Extensions: Parent Delegation models that break the class loading mechanism include hot deployment of code, and Tomcat's class loading mechanism is worth reading.

Note: It is recommended that some small partners do not know the parental delegation model of the class loading mechanism Full understanding of parental delegation model and custom ClassLoader Understand this article.

7 Extension: Dubbo's SPI mechanism

Previously, I also mentioned that the Dubbo framework is full of the application of SPI mechanism. It can be said that there are extensions everywhere. It is really the best application of SPI mechanism.However, instead of using the default Java SPI mechanism, Dubbo implemented its own set of SPI mechanisms.

So why doesn't Dubbo use Java's SPI mechanism?

There are two main reasons:

  1. Java's SPI mechanism instantiates all implementations of extension points at once. Initialization of extension implementations can be time consuming, but it can be a waste of resources if loaded unnecessarily.
  2. Java's SPI mechanism does not support Ioc and AOP, so Dubbo uses its own SPI mechanism: support for extension points IoC and AOP has been added, and one extension point can be directly set up to inject other extension points.

For these reasons, Dubbo has customized a set of SPI mechanisms to load its own extension points.The mechanism of Dubbo's SPI is not detailed here. Interested partners can go to Dubbo's website to see how to extend Dubbo's SPI.There are also source analysis articles for Duboo's SPI on its website.

8 Summary

Well, the SPI mechanism for Java is explained here. First summarize the previous points of knowledge:

  1. The use of the SPI mechanism in Java;
  2. The principle of the SPI mechanism in Java;
  3. Loading principle of JDBC driver;
  4. The role of the thread context class loader in JDBC-driven loading;
  5. The SPI mechanism of Duboo is briefly described.

Originality is not easy. Give me a compliment!

As the author's level is limited, if there are any errors in this article, please point out that thank you.

Reference resources:

1,http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

2, "Deep understanding of Java virtual machines"

Keywords: Programming Java JDBC MySQL Dubbo

Added by cyball on Sun, 29 Mar 2020 05:05:48 +0300