类的动态加载-双亲委派模型

java反射基础

Java 基础 - 反射机制详解 | Java 全栈知识体系 (pdai.tech)

类的动态加载

参考链接:类的动态加载

构造是和实例化也就是对象相关的。

静态代码块是在初始化的时候就调用的 Class.forName();就会调用静态代码块

forName,加载类时默认初始化

        Class.forName();    //默认初始化
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class.forName("Person",false,classLoader);//不进行初始化

类加载器的研究

类加载器,加载类时默认不初始化。

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<?> person = classLoader.loadClass("Person");
        Class<?> person = Class.forName("Person", false, classLoader); //两个代码作用相同

底层的原理,实现加载任意的类

java双亲委派

在这里插入图片描述

image-20240704094900997

Class<?> person1 = classLoader.loadClass("Person");处打个断点进行调试。

首先调用ClassLoader.classLoader(a)因为AppClassLoader中的classLoader参数是两个,所以调用到了其父类ClassLoader.classLoader(a,b)

之后ClassLoader.classLoader(a,b)调用AppClassLoader.classLoader(a), —> ClassLoader.classLoader(a,b)

在ClassLoader.classLoader(a,b)中就涉及到了双亲委派模型

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);             //检查类有没有被加载
        if (c == null) {                                //类没有被加载 进入
            long t0 = System.nanoTime();
            try {
                if (parent != null) {                   //还有父加载器的话,让父加载器loadClass   这里也就是ExtClassLoader
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

进入ExtClassLoader.loadCLass, 因为其中没有loadCLass所以又直接调用到了,上面CLass.loadClass方法。

这次

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {              //找不到父加载器了,因为bootstrap ClassLoader 不在java中
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);  //走到这一步 也不回找到 因为是一个普通的类 不会调用BootstrapClassLoader去加载
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);              //之后走到findClass("Person") 因为最后是在App CLassLoader中加载的 所以ExtClassLoader中先不跟了 返回的是null

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

之后return c=null逻辑又回到了 AppCLassLoader的loadCLass

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);             //检查类有没有被加载
        if (c == null) {                                //类没有被加载 进入
            long t0 = System.nanoTime();
            try {
                if (parent != null) {                   //还有父加载器的话,让父加载器loadClass   这里也就是ExtClassLoader
                    c = parent.loadClass(name, false);  //ExtCLassLoader返回null。所以c=null
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {                           //因为C=null 进入
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);                   //进到findClass("Person") 跟一下这里

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

下面跟进findClass(“Person”)

因为AppClassLoader总没有findClass方法,所以找到了其父类URLClassLoader的findClass

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);          //ucp是类的路径 URLClassPath类
                    if (res != null) {                                    //res不为空 进入
                        try {
                            return defineClass(name, res);                //主要跟一下defindClass
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

下图可以观察到 AppClassLoader调用findCLass时,ucp(查找路径)里面加入了file:/H:/java_des/target/classes/(我们项目Class路径),所以可以查到Person类,res不为空。

image-20240704135445797

跟一下URLCLassLoader.defineClass

image-20240704140107786

return 调用的是URLClassLoader的父类SecureClassLoader的defineClass方法

image-20240704140249660

return 调用的是CLassLoader的defineClass方法

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)     //这里name是类名,b是字节码
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); //在defineClass1完成类的加载(字节码) 是个native类
    postDefineClass(c, protectionDomain);
    return c;
}

之后一层一层返回加载的类,加载到了URLCLass.findClass中return defineClass(name, res);处。

下一步,也就是最终返回到了我们写的loadClass方法调用代码处,可以看到返回了Person类

在这里插入图片描述

总结

ClassLoader -> SecureClassLoader -> URLCLassLoader -> AppClassLoader (继承关系 父->子)

ClassLoader.loadClass -> URLCLass.findClass(重写方法)(路径中能找到类才进入defineCLass) ->SecureClassLoader.defineClass(从字节码加载类)->ClassLoader.defineClass

image-20240704144522395

利用

先编写并编译一个弹计算器的代码,放到一个指定路径,之后把项目中的Test.class删除(项目路径没有Test.class,看看是否能够通过类加载器的利用,找到类)

import java.io.IOException;

public class Test {
    static  {  //静态代码块
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

URLCLassLoader 任意类加载:file/http/jar 协议

public class LoadClassTest {
    public static void main(String[] args) throws Exception {
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///G:\\Java反序列化\\class_test\\")}); //指定Class的查找路径
        
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:9999/")}); //指定Class的查找路径
        
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://127.0.0.1:9999/Test.jar!/")}); //指定Class的查找路径
        Class<?> c = urlClassLoader.loadClass("Test"); //load Person类
        c.newInstance();  //实例化
    }
}

ClassLoader.defineClass 字节码加载任意类 私有

public class LoadClassTest {
    public static void main(String[] args) throws Exception {

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("G:\\Java反序列化\\class_test\\Test.class"));
        Class c = (Class) defineClass.invoke(classLoader, "Test",code, 0, code.length);  //对象 类名 字节码 字节码起始 字节码长度 defineClass返回的是Class<?> 这里也就是返回的Test.class
        c.newInstance(); //实例化 触发静态代码块
    }
}

Unsafe.defineClass 字节码加载 public类但是不能直接调用,需要先反射调用public方法实例化类

Unsafe类中defineClass方法是public的,但是是个单例模式,不能直接调用defineClass()。

看到有个getUnsafe方法,是public的,但是直接调用Unsafe.getUnsafe()是会报错的因为有个安全检查。

最后找到theUnsafe属性

private static final Unsafe theUnsafe = new Unsafe();

所以反射调用theUnsafe属性去实例化Unsafe

public native Class<?> defineClass(String name, byte[] b, int off, int len,
                                   ClassLoader loader,
                                   ProtectionDomain protectionDomain);
public class LoadClassTest {
    public static void main(String[] args) throws Exception {

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        byte[] code = Files.readAllBytes(Paths.get("G:\\Java反序列化\\class_test\\Test.class"));

        Class unsafe = Unsafe.class;
        Field theUnsafeField = unsafe.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        Unsafe unsafe1 = (Unsafe) theUnsafeField.get(null);
        Class<?> test = unsafe1.defineClass("Test", code, 0, code.length, classLoader, null);
        test.newInstance();
    }
}

Spring里面可以直接生成

  • 30
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值