Parental delegation mechanism and its disadvantages

Parental delegation mechanism?

reference resources:

https://zhuanlan.zhihu.com/p/185612299

https://www.jianshu.com/p/09f73af48a98

Parental delegation mechanism is the default mechanism for JVM class loading. Its principle is that when a class loader receives a class loading task, it will first hand it over to its parent loader to complete it. Therefore, the final loading task will be passed to the bootstrap classloader at the top level. It will try to load it by itself only when the parent loader cannot complete the loading task. In the order from parent to subset, the class loader mainly includes the following:

  • Bootstrap classloader: it is mainly responsible for loading the core class library (java.lang. * etc.) and JVM_ In the home / lib directory, construct ExtClassLoader and APPClassLoader.
  • ExtClassLoader (extended class loader): it is responsible for_ Runtime_ Home > / lib / ext or the class library in the location specified by the system variable java.ext.dir is loaded into memory. Developers can use the standard extension class loader directly.
  • AppClassLoader (system class loader): it is mainly responsible for loading the main function class of the application. It is responsible for loading the class library specified in the system CLASSPATH into memory. Developers can use the system class loader directly.
  • Custom class loader: mainly responsible for loading the main function class of the application

advantage:

One can avoid repeated loading of classes and tampering with the core API of java.

What are the shortcomings of the parental delegation mechanism?

The limitation is that the parent loader cannot load the classes in the child classloader path. What if the basic class calls the user class?

The most typical example is that this extends our discussion on the defects of the parent delegation mechanism. The interface: java.sql.Driver is defined in the java.sql package. The package is located in jdk\jre\lib\rt.jar. Other corresponding classes and interfaces are also provided in the java.sql package, such as the management driver class: DriverManager class, Obviously, the java.sql package is loaded by the bootstrap classloader loader; The interface implementation class com.mysql.jdbc.Driver is a class library implemented by a third party and loaded by the AppClassLoader loader. Our problem is that when the DriverManager obtains the link again, it must be loaded into the com.mysql.jdbc.Driver class. This is that the classes loaded by bootstrappclassloader use the classes loaded by AppClassLoader, which is obviously contrary to the principle of the two parent delegation mechanism, How does it solve this problem?

Simply put, there is a method in the startup class loader to obtain the application class loader, which is the thread context class loader. It can be set through the thread. Setcontextclassloader() method. If there is no special setting, it will inherit from the parent class. Generally, the application class loader is used by default

Add: Java itself has a set of resource management service JNDI, which is placed in rt.jar and loaded by the startup class loader. JNDI is a Java Naming and Directory Interface: in short, it is to name resources and find resources according to their names.

Detailed explanation:
   // 1. Load data access driver
        Class.forName("com.mysql.jdbc.Driver");
        //2. Connect to the data "library"
        Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

The core is that this Class.forName() triggers the loading of mysql Driver. Let's take a look at the implementation of Driver interface by mysql:

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!");
        }
    }
}

You can see that Class.forName() actually triggers the static code block, and then registers a Driver implementation of mysql with the Driver manager.
At this time, when we get the connection through the DriverManager, we just need to traverse all the current Driver implementations, and then select one to establish the connection.

The Driver interface is provided by java to operate the database (the specific implementation is implemented by the manufacturer), and the DriverManager class is the implementation to manage this Driver.

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;
}
public class DriverManager {


    // List of registered JDBC drivers is used here to save the specific 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);

    }
    

}
How to break the parental delegation mechanism?

After JDBC 4.0, spi is supported to register this Driver. The specific method is to specify which Driver is currently used in the META-INF/services/java.sql.Driver file in the mysql jar package, and then directly when using it:

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

You can see that the connection is obtained directly here, and the above Class.forName() registration process is omitted.
Now, let's analyze the original process of using this spi service model:

  • First, get the specific implementation class name "com.mysql.jdbc.Driver" from the META-INF/services/java.sql.Driver file
  • Second, load this class, which can only be loaded with class.forName("com.mysql.jdbc.Driver")

Well, here's the problem. Class.forName() loads using the caller's classloader. The caller's DriverManager is in rt.jar. Classloader starts the class loader, and com.mysql.jdbc.Driver is definitely not in < Java_ Home > / lib, so this class in MySQL cannot be loaded. This is the limitation of the parent delegation model. The parent loader cannot load the classes in the child classloader path.

So, how to solve this problem? According to the current situation, the mysql drvier can only be loaded by the application class loader, so we only need to have a method to obtain the application class loader in the startup class loader, and then load it through it. This is called the thread context loader.
The thread context class loader can be set through the thread. Setcontextclassloader() method. If there is no special setting, it will inherit from the parent class. Generally, the application class loader is used by default

Obviously, the thread context class loader allows the parent class loader to load classes by calling the child class loader, which breaks the principle of the two parent delegation model

Use of context class loader?

Now let's take a look at how the DriverManager uses the thread context class loader to load the Driver class in the third-party jar package.

public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    private static void loadInitialDrivers() {
        //Omit code
        //Here is to find the drivers registered by each sql vendor through spi in its own jar package
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        try{
             while(driversIterator.hasNext()) {
                driversIterator.next();
             }
        } catch(Throwable t) {
                // Do nothing
        }

        //Omit code
    }
}

When using, we directly call the DriverManager.getConn() method, which will naturally trigger the execution of static code blocks and start loading drivers
Then let's look at the specific implementation of ServiceLoader.load():

    public static <S> ServiceLoader<S> load(Class<S> service) {
        //Get the thread context class loader, and then construct a ServiceLoader. The subsequent specific search process
        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);
    }

Next, in the loadInitialDrivers() method of DriverManager, there is a sentence driveriterator. Next();, Its specific implementation is as follows:

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //cn here is the name of the Driver implementation class registered by the manufacturer in META-INF/services/java.sql.Driver file
               //The loader here is the thread context class loader passed in when constructing ServiceLoader
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
         //Omit some codes
        }

Now, we have successfully obtained the application class loader (or customized and then stuffed into the thread context) through the thread context class loader. At the same time, we have also found the specific implementation class name of the driver registered by the manufacturer in the child jar package, In this way, we can successfully load the classes placed in the third-party application package in the DriverManager in the rt.jar package.

Tags: Java jvm

Posted on Wed, 22 Sep 2021 04:18:48 -0400 by markmax33