Java--ClassLoader 类加载机制与重写类加载

1.ClassLoader

Java是依赖JVM实现的跨平台开发,程序运行前需要先编译class文件,

Java类初始化的时候会调用java.lang.Classloader来加载字节码,

然后ClasssLoader调用JVM的native方法来定义一个java.lang.Class实例。

 

2.Java类

public class TestHello {
    public String hello(){
        return "hello,world!";
    }
}

这里编译成一个java文件

 

 使用javap -c 命令反汇编class文件

 

 JVM再执行我们的TestHello时候会先解析class的二进制内容,其实就是javap命令生成的字节码。

 

 

3.类加载方法

所有的java类都必须经过JVM加载后才能运行,ClassLoader主要作用就是用于Java类文件的加载。

在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)(接触较少App ClassLoader(系统类加载器)(直接加载我们的代码AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类。

 可以这么说 用java.io.File.class.getClassLoader()取得是null的对象,就是用Bootstrap去加载的,以为它是用C++去实现的,所以当然得不到对应的对象了!

ClassLoader类有如下核心方法:

  1. loadClass(加载指定的Java类)
  2. findClass(查找指定的Java类)
  3. findLoadedClass(查找JVM已经加载过的类)
  4. defineClass(定义一个Java类)
  5. resolveClass(链接指定的Java类)

 

4.Java动态加载方式

Java的加载方式有显式与隐式。

显式:Java反射或者ClassLoader来动态加载一个类对象。

隐式:类名.方法名()或者new()类的实例。

我们可以自定义类加载器去加载任意的类

// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");

// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

 

 

5.重写classloader

可以通过重写classloader类来加载字节码(为一个不存在的类),加载到JVM里面去,然后通过反射去调用这个类来实例化他的对象调用他的方法。这里就以TestHelloWorld类为例,先注释掉之前写的TestHelloWorld,

 

 完整重写代码

package com.anbai.sec.classloader;

import java.lang.reflect.Method;

/**
 * Creator: yz
 * Date: 2019/12/17
 */
public class TestClassLoader extends ClassLoader {

    // TestHelloWorld类名
    public static String TEST_CLASS_NAME = "com.anbai.sec.classloader.TestHelloWorld";

    // TestHelloWorld类字节码
    public static byte[] TEST_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
            16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
            1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
            101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
            114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
            32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
            115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
            116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
            0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
            1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
            0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
            0, 0, 0, 2, 0, 12
    };

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(TEST_CLASS_NAME)) {
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
        }

        return super.findClass(name);
    }

    /**
     * 使用自定义类加载器加载TestHelloWorld类字节码并调用hello方法示例,等价于如下代码:
     * <p>
     *
     * </p>
     *
     * @param args
     */
    public static void main(String[] args) {
        // 创建自定义的类加载器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定义的类加载器加载TestHelloWorld类
            Class testClass = loader.loadClass(TEST_CLASS_NAME);

            // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射获取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射调用hello方法,等价于 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

 

首先继承ClassLoader 重写他的方法,这里定义了TEST_CLASS_NAME就是TestHelloWorld的包名到名字

 

 

 

看这个findClass,做一个判断,名字是否是我们所写的TestHelloWorld,然后return defineClass

是调用JVM的方法去定义TestHellowWorld类;

 

 

 

如果不是我们想创建的对象就 

return super.findClass(name);

用super调用父类的findclass去创建。

 

这里代码的main方法就是这样的流程,注意看注释

创建自定义的类加载器loader对象-->用我们重写的类加载器去加载TestHelloWorld类

-->然后通过反射该类,获取对应对象-->然后反射调用对象来invoke调用到hello方法

-->最后通过hello方法的返回str也就是hello world 输出

/**
     * 使用自定义类加载器加载TestHelloWorld类字节码并调用hello方法示例,等价于如下代码:
     * <p>
     *
     * </p>
     *
     * @param args
     */
    public static void main(String[] args) {
        // 创建自定义的类加载器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定义的类加载器加载TestHelloWorld类
            Class testClass = loader.loadClass(TEST_CLASS_NAME);

            // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射获取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射调用hello方法,等价于 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

ok来打断点走一遍整个调用流程首先是在加载我们的TestHelloWorld类


 

然后以为名字是if判断对应的名字,所以会进入到defineClass方法里面

 

 

 

 defineClass是会返回一个对象的


 

 

 我们用的是58行断点那里的testClass来接收这个返回值

这个返回值也就是TestHelloWorld的对象了,然后我们通过反射去调用这个对象

 

 

 

然后通过testInstance来反射获取到Hello方法

 

 

 最后就是通过反射调用hello方法来执行,返回给str 输出了hello world:

 

 

 

 

 



刚开始理解起来确实有些难,慢慢来把,学习之路,慢就是快~
posted @ 2021-12-22 21:23  Erichas  阅读(2182)  评论(2编辑  收藏  举报