There is no book on the market that talks about Java class loaders

1. Thinking of a Programmer

Everyone knows that Tomcat handles business, what does it rely on? In the end, it is the Servlet that we wrote ourselves. You may say that you don't write servlet, you use spring MVC, that's because someone wrote it for you, you just need to configure it. Here, there is a boundary, Tomcat is a container, and the related jar packages of the container are placed under the lib of its own installation directory; for us, it is a business, a webapp, our servlet, whether it is custom or spring mvc The DispatcherServlet is placed under WEB-INF/lib in our war package. Students who have read the previous article know that the two are loaded by different class loaders. In the implementation of Tomcat, the webappclassloader will be entrusted to load the servlet in the WAR package, and then the corresponding servlet will be generated by reflection. When a subsequent request comes, call the service method of the generated servlet.

In org.apache.catalina.core.StandardWrapper#loadServlet, that 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);
    }

In the above figure, instanceManager will be used to generate a servlet instance according to the servletClass specified in the parameter. The newInstance code is as follows, mainly using the classloader of the current context to load the servlet, and then generating the servlet object by reflection.

What we focus on is the forced transfer circled by the red box: why can the object loaded by the webappclassloader be converted to the servlet loaded by the Tomcat common classloader? It stands to reason that the classes loaded by two different class loaders are isolated from each other, shouldn't a ClassCastException be thrown? Seriously, I've looked through quite a few books and never mentioned this, and it's vague even online.

Another one, about SPI. In SPI, the specification is mainly specified by the java community, such as JDBC, there are so many manufacturers, mysql, oracle, postgre, everyone has their own jar package, if there is no JDBC specification, we estimate that we have to target the implementation class programming of each manufacturer If the code you wrote for the mysql database is replaced by oracle, the code will definitely not be able to run without changing the code. Therefore, the JCP organization has formulated the JDBC specification, and a bunch of interfaces are specified in the JDBC specification. We usually develop and only need to program for the interface, and how to implement it is handed over to the manufacturers, and the manufacturers implement the JDBC specification. Here is an example of code, oracle.jdbc.OracleDriver implements java.sql.Driver, and at the same time, in the static initialization block of oracle.jdbc.OracleDriver, there is the following code:

    static {
        try {
            if (defaultDriver == null) {
                defaultDriver = new oracle.jdbc.OracleDriver();
                DriverManager.registerDriver(defaultDriver);
            }
    // 省略
    }

Among them, the sentence marked in red means that the Oracle Driver needs to register itself with the JDBC interface. The implementation of java.sql.DriverManager#registerDriver(java.sql.Driver) is as follows:

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

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

        registerDriver(driver, null);
    }

It can be seen that the parameter of the registerDriver(java.sql.Driver) method is java.sql.Driver, and the parameter we pass is the oracle.jdbc.OracleDriver type. These two types are loaded by different class loaders (java. .sql.Driver is loaded by the startup class loader of jdk, and oracle.jdbc.OracleDriver, if it is a web application, it is loaded by the webapp classloader of tomcat, anyway, it is not loaded by jdk anyway), such two types , even the class loader is different, how can it be converted normally, why not throw ClassCastException?

Second, the key to convert the classes loaded by different class loaders

After the observation of the above two examples, I don't know if you have found it. We all convert an implementation into an interface. Perhaps, this is the crux of the matter. We can boldly speculate that based on the class-based parent delegation mechanism, when loading the implementation class, the jvm will also trigger the loading when it encounters other classes referenced in the implementation class. During the loading process, loadClass will be triggered, for example, loading webappclassloader When loading oracle.jdbc.OracleDriver, the loading of java.sql.Driver is triggered, but the webappclassloader obviously cannot load java.sql.Driver, so it will be delegated to the class loading of jdk, so, finally, in oracle.jdbc.OracleDriver The referenced java.sql.Driver is actually loaded by the jdk class loader. The type java.sql.Driver of the driver parameter in registerDriver(java.sql.Driver driver) is also loaded by the class loader of jdk, the two are the same, so they can be converted to each other naturally.

Here is a summary (not necessarily correct), under the condition that the following conditions are met at the same time:

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

  • Precondition 2. In the implementation jar package, define the implementation class of Test, such as TestImpl. (But don't include the interface in the class, if you say it can't be compiled, then put the interface jar package on the classpath)

  • Precondition 3. The interface jar package is loaded by interface_classLoader, and the implementation jar package is loaded by impl_classloader, of which impl_classloader will delegate to interface_classLoader when it cannot be loaded by itself

Then, the implementation class of the Test interface defined in the implementation jar, the object generated by reflection, can be converted to the Test type.

After guessing, it is the verification process.

3. Proof

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 {
}

Under cmd, execute:

D:\classloader_interface>javac ITestSample.java
D:\classloader_interface>jar cvf interface.jar ITestSample.class
已添加清单
正在添加: ITestSample.class(输入 = 103) (输出 = 86)(压缩了 16%)

At this point, an interface jar package named interface.jar can be generated in the current directory.

2. Define the implementation jar of the interface

In a different directory, a new implementation class is created.

D:\classloader_impl\TestSampleImpl.java

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

}

Compile, package:

D:\classloader_impl>javac -cp D:\classloader_interface\interface.jar TestSampleI
mpl.java
 
D:\classloader_impl>jar -cvf impl.jar TestSampleImpl.class
已添加清单
正在添加: TestSampleImpl.class(输入 = 221) (输出 = 176)(压缩了 20%)

Please pay attention to the red line above, without compiling.

3. Test

The idea of ​​the test is to use a urlclassloader to load ITestSample in interface.jar, use another URLClassLoader to load TestSampleImpl in impl.jar, and then use java.lang.Class#isAssignableFrom to judge whether the latter can be converted into 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("实现类能转否?:"  + iTestSampleClass.isAssignableFrom(testSampleImplClass));

    }

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

}

It prints as follows:

4. Extension test 1

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

Before the change, urlClassloader was used as parentClassloader:

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

After the change, if it is not passed, it will use the jdk application class loader as the parent by default:

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

The print 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.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
    at java.net.URLClassLoader$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.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$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

The result is that, on line 23, Class<?> testSampleImplClass = implUrlClassLoader.loadClass("TestSampleImpl"); An error is reported here, indicating that ITestSample cannot be found.

This is because, after the implUrlClassLoader is loaded, the implicit loading of ITestSample is triggered. Which loader will be used for this implicit loading? If there is no default indication, the current class loader is used, and the current class The loader is implUrlClassLoader, but this class loader starts to load ITestSample, which follows parental delegation, and its parent loader is appclassloader, (jdk's default application class loader), but appclassloader cannot load ITestSample at all, so it is still Give implUrlClassLoader, but implUrlClassLoader can't load either, so an exception is thrown.

5. Extension test 2

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

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

This time, look at the results, maybe you guessed it?

No error was reported this time, after all, almostSameUrlClassLoader knows where to load ITestSample, but the final result shows that the class of the implementation class cannot be converted to ITestSample.

6. Extension test 3

To be honest, some students may not be very familiar with java.lang.Class#isAssignableFrom. Let's change to one that you are less familiar with. How about?

        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); // 将 o 转成 接口的那个类
        System.out.println(cast);

result:

If you replace it with the following, you will be fine:

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

implement:

If you are struggling on the road of JAVA and want to get a high salary in the IT industry, you can participate in our training camp courses, choose the most suitable course for you to learn, and the technical experts will teach you personally. After 7 months, you can enter a famous company and get a high salary . Our courses include: Java engineering, high performance and distributed, high architecture. Performance tuning, Spring, MyBatis, Netty source code analysis and big data and other knowledge points. If you want to get a high salary, want to study, want good employment prospects, want to gain an advantage in competition with others, want to enter Ali for an interview but are worried about the interview, you can come, q group number: 956011797

Note: Join group requirements

1. Those who have 1-5 work experience, do not know where to start in the face of the current popular technology, and need to break through the technical bottleneck can be added.

2. After staying in the company for a long time, I lived very comfortably, but the interview hit a wall when I changed jobs. Those who need to study in a short period of time and change jobs to get high salaries can be added.

3. If you have no work experience, but have a solid foundation, you can add them if you are proficient in the working mechanism of java, common design ideas, and common java development frameworks.

4. I feel that I am very good, and I can handle general needs. However, the knowledge points learned are not systematic, and it is difficult to continue to make breakthroughs in the technical field.

5. Ali Java senior Daniel live broadcast to explain knowledge points, share knowledge, sort out and summarize years of work experience, and lead everyone to comprehensively and scientifically establish their own technical system and technical awareness!

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324138502&siteId=291194637