A programmer’s thinking

As we all know, Tomcat handles business, by what? Ultimately, we wrote our own servlets. You might say you don’t write servlets, you use Spring MVC, that’s written for you, you just need to configure it. Here, there is a boundary. Tomcat counts as a container, and the jar packages associated with the container are placed under its own installation directory, lib; For us, it’s business, it’s WebApp, our servlet, whether it’s custom, or Spring MVC DispatcherServlet, is in our WAR package web-INF /lib. As those of you who have read the previous article know, they are loaded by different class loaders. In the Tomcat implementation, the WebAppClassLoader is delegated to load the servlets in the WAR package and then reflects to generate the corresponding servlets. When subsequent requests come in, you simply call the service method of the generated servlet.

In the org. Apache. Catalina. Core. StandardWrapper# loadServlet, is responsible for generating the servlet:

org.apache.catalina.core.DefaultInstanceManager#newInstance(java.lang.String)@Override public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException { Class<? > clazz = loadClassMaybePrivileged(className, classLoader);return newInstance(clazz.newInstance(), clazz);
    }
Copy the code

In the figure above, instanceManager is used to generate servlet instances based on the servletClass specified in the parameters. The newInstance code uses the current context’s classloader to load the servlet and then reflects the servlet object.

Our focus is on the strong turn circled in red: why can objects loaded by webAppClassLoader be converted to servlets loaded by Tomcat Common ClassLoader? Two different classloaders are supposed to load classes that are isolated from each other. Shouldn’t you throw a ClassCastException? Seriously, I’ve been reading a lot of books, and I’ve never mentioned it, and even the Internet is pretty vague.

One more question about SPI. In SPI, the Java community mainly specifies the specification, such as JDBC. There are so many manufacturers, mysql, Oracle, postGRE, and everyone has their own JAR packages. If there were no JDBC specification, we would probably have to program for the implementation classes of each manufacturer, and then migration would be troublesome. The code you wrote for mysql will not run if you change it to Oracle. Therefore, THE JCP organization developed the JDBC specification, JDBC specification specified a bunch of interfaces, we usually develop, only need to program for the interface, and how to achieve, to each manufacturer bai, by the manufacturer to achieve the JDBC specification. Oracle JDBC.OracleDriver implements java.sql.Driver, and in the static initialization block of oracle.jdbc.

    static {
        try {
            if(defaultDriver == null) { defaultDriver = new oracle.jdbc.OracleDriver(); DriverManager.registerDriver(defaultDriver); } // omit}Copy the code

Java.sql.DriverManager#registerDriver(java.sql.Driver) registers itself with the JDBC interface.

java.sql.DriverManager#registerDriver(java.sql.Driver) 

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

        registerDriver(driver, null);
    }
Copy the code

As you can see, the registerDriver(java.sql.Driver) method takes java.sql.Driver as an argument, and we pass the oracle.jdbc.oracleDriver as an argument. Java.sql.Driver is loaded by the JDK’s startup classloader, while Oracle.jdbc.oracleDriver is loaded by tomcat’s WebAppClassloader if it is a Web application. ClassCastException is not a class loader, but a ClassCastException is not a class loader.

Two, different class loaders loaded by the key can be converted

I don’t know if you noticed from these two examples, but we’re converting an implementation to an interface. Perhaps that is the point. It is safe to assume that the parent delegate mechanism based on the class, when the implementation class is loaded, the JVM encounters other classes referenced by the implementation class, and the loadClass is triggered during the loading process, for example, The webAppClassLoader triggers the loading of java.sql.Driver when oracle.jdbc.oracleDriver is loaded. Java.sql.Driver is not loaded by the webAppClassloader, so it delegates to JDK class loading. It is actually loaded by the JDK’s classloader. Java.sql.Driver is the type of the Driver parameter in registerDriver(java.sql.Driver). Java.sql.Driver is loaded by the JDK classloader.

To sum up, if the following conditions are met at the same time:

  • Prerequisites 1. In the jar package, define an interface Test

  • 2. In the implementation JAR, define the implementation class of Test, such as TestImpl. (But don’t include the interface in the class. If you say you can’t compile, put the jar package in your classpath.)

  • The interface JAR package is loaded by interface_classLoader. The implementation JAR package is loaded by IMPL_classloader. Delegated to interface_classLoader

Then, the implementation class defined for the Test interface in the implementation JAR, reflecting the generated object, can be converted to type Test.

Guess said, is the process of verification.

Third, verification

1. Define the interface JAR
D:\classloader_interface\ITestSample.java  

/**
 * desc:
 *
 * @author : 
 * creat_date: 2019/6/16 0016
 * creat_time: 19:28
 **/
public interface ITestSample {
}
Copy the code

Under CMD, run the following command:

Java D:\ classloader_Interface >jar CVF interface.jar itestsample. class The list has been added Adding: itestSample.class (input = 103) (output = 86)(compressed by 16%)Copy the code

In this case, the interface JAR package named interface-. jar can be generated in the current directory.

2. Define the implementation JAR of the interface

A new implementation class is created in a different directory.

D:\classloader_impl\TestSampleImpl.java

/**
 * Created by Administrator on 2019/6/25.
 */
public class TestSampleImpl implements  ITestSample{

}
Copy the code

Compile, package:

D:\classloader_impl>javac -cp D:\classloader_interface\interface.jar TestSampleI mpl.java D:\classloader_impl>jar -cvf Jar TestSampleImpl. Class Added list adding: TestSampleImpl. Class (input = 221) (output = 176)(compressed by 20%)Copy the code

Note the red line above, not compiled however.

3, test,

Jar (TestSampleImpl). Jar (TestSampleImpl) Then use java.lang.Class#isAssignableFrom to determine if the latter can be converted to the former.

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * desc:
 *
 * @author : caokunliang
 * creat_date: 2019/6/14 0014
 * creat_time: 17:04
 **/
public class MainTest {


    public static void testInterfaceByOneAndImplByAnother()throws Exception{
        URL url = new URL("file:D:\\classloader_interface\\interface.jar"); URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}); Class<? > iTestSampleClass = urlClassLoader.loadClass("ITestSample");


        URL implUrl = new URL("file:D:\\classloader_impl\\impl.jar"); URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl}, urlClassLoader); Class<? >testSampleImplClass = implUrlClassLoader.loadClass("TestSampleImpl");


        System.out.println("Can the implementation class roll? :"  + iTestSampleClass.isAssignableFrom(testSampleImplClass));

    }

    public static void main(String[] args) throws Exception {
        testInterfaceByOneAndImplByAnother(); }}Copy the code

Print the following:

4. Extend Test 1

If we make the following changes, guess what? The main differences here are:

UrlClassloader as parentClassloader:

  URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl}, urlClassLoader);
Copy the code

The JDK’s application classloader is used as parent by default:

  URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl});
Copy the code

The printed result is:

Exception in thread "main" java.lang.NoClassDefFoundError: ITestSample
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
    at java.net.URLClassLoader.accessThe $100(URLClassLoader.java:73)
    at java.net.URLClassLoaderThe $1.run(URLClassLoader.java:367)
    at java.net.URLClassLoaderThe $1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at MainTest.testInterfaceByOneAndImplByAnother(MainTest.java:23)
    at MainTest.main(MainTest.java:33)
Caused by: java.lang.ClassNotFoundException: ITestSample
    at java.net.URLClassLoaderThe $1.run(URLClassLoader.java:372)
    at java.net.URLClassLoaderThe $1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 13 more
Copy the code

The result is line 23, Class<? > testSampleImplClass = implUrlClassLoader.loadClass(“TestSampleImpl”); The ITestSample cannot be found.

This is because, after loading the implUrlClassLoader, an implicit loading of ITestSample is triggered. Which loader will be used to load the implicit loading of ITestSample? The current classloader is implUrlClassLoader, but the classloader starts loading ITestSample, which follows the parent delegate, and whose parent loader is appClassLoader (the JDK’s default application classloader). But AppClassLoader couldn’t load ITestSample at all, so it returned to implUrlClassLoader, but implUrlClassLoader couldn’t load either, so it threw an exception.

5. Extend Test 2

We make one more change, the same as the last test, except this time we pass in a special classloader as its parentClassLoader. What makes it special is that the almostSameUrlClassLoader is exactly the same as the previous classloader that loaded interface-.jar, just a new instance.

        URLClassLoader almostSameUrlClassLoader = new URLClassLoader(new URL[]{url});
        URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl}, almostSameUrlClassLoader);
Copy the code

This time, let’s see what happens. Maybe you guessed it?

After all, the almostSameUrlClassLoader knows where to load ITestSample. However, the final result shows that the class that implements the class does not convert to ITestSample.

6. Extend Test 3

To be honest, some of you may not be familiar with java.lang.Class#isAssignableFrom, so how about we switch to something even less familiar to you?

        URL implUrl = new URL("file:D:\\classloader_impl\\impl.jar"); URLClassLoader almostSameUrlClassLoader = new URLClassLoader(new URL[]{url}); URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl}, almostSameUrlClassLoader); Class<? >testSampleImplClass = implUrlClassLoader.loadClass("TestSampleImpl");
        Object o = testSampleImplClass.newInstance(); Object cast = iTestSampleClass.cast(o); // The class that converts O into an interface system.out.println (cast);Copy the code

Results:

This should not be a problem if you switch to the following:

        URL implUrl = new URL("file:D:\\classloader_impl\\impl.jar"); URLClassLoader almostSameUrlClassLoader = new URLClassLoader(new URL[]{url}); URLClassLoader implUrlClassLoader = new URLClassLoader(new URL[]{implUrl}, urlClassLoader); Class<? >testSampleImplClass = implUrlClassLoader.loadClass("TestSampleImpl");
        Object o = testSampleImplClass.newInstance();
        Object cast = iTestSampleClass.cast(o);
        System.out.println(cast);
Copy the code

Perform: