Java itself has a resource management service, JNDI, which is placed in rt.jar and loaded by the startup class loader. For example, Java provides a Driver interface for database operations:

public interface Driver {
    Connection connect(String url, java.util.Properties info) throws SQLException;
    boolean acceptsURL(String url) throws SQLException;
    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;
    int getMajorVersion();
    int getMinorVersion();
    boolean jdbcCompliant();
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
Copy the code

A DriverManager is then provided to manage the concrete implementation of these drivers:

Public class DriverManager {// List of registered JDBC drivers Stores the implementation of all drivers private final Static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { 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) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); }}Copy the code

Most of the code is omitted here, so you can see that we must register with registerDriver() in DriverManager before we can use the database driver.

Without breaking the parent delegate model (without using JNDI services)

The mysql driver is loaded as follows:

// 1. Load the data access Driver class.forname (" com.mysql.jdbc.driver "); / / 2. The Connection to the data "library" Connection conn = DriverManager. GetConnection (" JDBC: mysql: / / localhost: 3306 / mydb? characterEncoding=GBK", "root", "");Copy the code

Class. ForName () triggers the load of the mysql Driver.

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

As you can see, class.forname () actually triggers the static code block and registers a Driver implementation of mysql with DriverManager. At this point, when we use DriverManager to get a connection, we simply iterate through all the current Driver implementations and select one to establish a connection.

A condition that breaks the parental delegation model

After JDBC4.0, began to support the use of spi way to register the Driver, the particular way is in the jars of mysql meta-inf/services/Java, SQL, indicate the current Driver file which is using the Driver, Then use it directly like this:

Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb? characterEncoding=GBK", "root", "");Copy the code

As you can see, you get the connection directly, bypassing the class.forname () registration process above. Now let’s take a look at the original process using the SPI service pattern:

  • First, from the meta-inf/services/Java, SQL. The Driver file for concrete implementation class name “. Com. Mysql. JDBC Driver”
  • Second, load the class, which must only be loaded with class.forname (” com.mysql.jdbc.driver “)

Well, class.forname () is loaded using the caller’s Classloader. DriverManager is in rt.jar. Classloader is used to start the Classloader. Com.mysql.jdbc.driver is not in <JAVA_HOME>/lib, so it is impossible to load the mysql class. This is a limitation of the parent delegate model. The parent loader cannot load classes in the child classloader’s path.

So how can this problem be solved? The drvier of mysql can only be loaded by the application class loader, so we only need to get the application class loader in the startup class loader and load it through it. This is called a thread context loader. Thread context class loader can Thread. SetContextClassLoaser () method is set up, if not special setup can be inherited from the parent class, and is generally used by default application class loader

Obviously, the thread-context classloader allows the parent class loader to load classes by calling the child class loader, breaking the principle of the parent-delegate model

Now let’s look at how DriverManager uses the thread-context class loader to load the Driver class from a third-party JAR package.

public class DriverManager { static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() {public static void loadInitialDrivers() {public static void loadInitialDrivers(); loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); }} catch(Throwable t) {// Do nothing}Copy the code

When used, we call driverManager.getConn () directly to trigger the static code block to start loading the driver. Then we’ll look at the serviceloader.load () implementation:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
        return new ServiceLoader<>(service, loader);
    }
Copy the code

As you can see, the core is to get the thread-context class loader, and then construct a ServiceLoader. We won’t go into the details of how to find the ServiceLoader, but we just need to know that the ServiceLoader already has the thread-context class loader. Next, the loadInitialDrivers() method of DriverManager has a driversiterator.next (); , its concrete implementation is as follows:

private S nextService() { if (! hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<? > c = null; Try {/ / cn is makers here in the meta-inf/services/Java, SQL. The Driver file registration / / the name of the Driver specific implementation class of the loader is here before pass in the ServiceLoader structure in the thread context class loader = c  Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } // omit some code}Copy the code

Now that we’ve managed to get the application class loader (or a custom one that is then plugged into the thread context) through the thread context class loader, we’ve also found the driver implementation class name registered by the vendor in the child jar package. This way we can successfully load the classes in the third party application package from DriverManager in the rt.jar package.

conclusion

Mysql driver loading process

  • First, get the thread-context classloader, which in turn gets the application classloader (or perhaps a custom classloader)
  • In the second place, from meta-inf/services/Java, SQL. The Driver file for concrete implementation class name “. Com. Mysql. JDBC Driver”
  • Third, the Driver class is loaded through the thread-context classloader, avoiding the drawbacks of the parent delegate model

Obviously, the SPI service used by the mysql driver really does break the parent delegate model, after all, the parent class loader loads classes in the child path.