打破Java双亲委派机制的方法与原理解析

如何打破双亲委派机制

上一篇我们讲到什么是双亲委派机制:https://refblogs.com/article/441

1. 什么是双亲委派机制

双亲委派机制(Parent Delegation Model)是Java中类加载机制的一种规范。在该机制下,当类加载器收到类加载请求时,它首先会将请求转发给父类加载器。只有当父类加载器无法找到所需的类时,子加载器才会尝试加载。这种层级的类加载机制建立了一种优化策略,保证了类的唯一性和防止类的重复加载。

2. 为什么要打破双亲委派机制

在某些特定的场景下,我们可能需要打破双亲委派机制。一种常见的情况是,我们希望自定义的类加载器能够加载某些特定的类,而不是委托给父类加载器。这样可以灵活地控制类的加载方式,实现一些自定义的需求。

3. 如何打破双亲委派机制

要打破双亲委派机制,我们需要自定义一个类加载器,并重写其中的加载逻辑。下面是一个简单的示例代码:

package com.example;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.Files;

/**
 * @author niuxiangqian
 * @version 1.0
 * @date 2023/8/31 22:24
 **/
public class CustomClassLoader extends ClassLoader {
    private String path;
    private String classLoaderName;

    //Path类文件的路径,classLoaderName类文件的名字
    public CustomClassLoader(String path, String classLoaderName) {
        this.path = path;
        this.classLoaderName = classLoaderName;
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 判断是否需要自定义加载的类
        if (name.startsWith("com.example")) {
            System.out.println("自定义加载器加载类:" + classLoaderName + "->" + name);
            return findClass(name);
        }

        // 其他情况委托给父类加载器
        return super.loadClass(name);
    }

    //用于寻找类文件
    @Override
    public Class<?> findClass(String name) {
        byte[] b = loadFileClassData(name); //将找到类文件以二进制的数组形式加载
        return defineClass(name, b, 0, b.length);  //JDK默认实现
    }

    //用于加载类文件,如果文件数据已经加密,可以在方法内进行解密
    private byte[] loadFileClassData(String name) {
        name = path + name.replace('.', '/')  + ".class"; //通过路径和文件名查找文件
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = Files.newInputStream(new File(name).toPath());
            out = new ByteArrayOutputStream();
            int i = 0;
            while ((i = in.read()) != -1) { //!= -1是没有读完的条件
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return out.toByteArray();
    }
}

在上述示例中,我们通过自定义类加载器CustomClassLoader,重写了loadClass方法,对需要自定义加载的类(这里以"com.example"开头的类)进行自定义加载逻辑,其他类则委托给父类加载器。

再写一个被加载的类

package com.example;

/**
 * @author niuxiangqian
 * @version 1.0
 * @date 2023/8/31 22:28
 **/
public class CustomClass {
    public void hello(){
        System.out.println("Hello, I'm Niuxiangqian");
    }
}

下面我们来测试一下这个自定义类加载器的效果:

package com.example;

/**
 * @author niuxiangqian
 * @version 1.0
 * @date 2023/8/31 22:27
 **/
public class Test1 {
    public static void main(String[] args) throws Exception {
        // 创建自定义类加载器
        CustomClassLoader classLoader = new CustomClassLoader("D:\\IdeaProjects\\nBlog\\blog-web\\target\\classes\\", "customClassLoader");

        // 加载自定义的类
        Class<?> customClass = classLoader.loadClass("com.example.CustomClass");

        // 创建自定义类的实例
        Object instance = customClass.getDeclaredConstructor().newInstance();

        // 调用自定义类的方法
        customClass.getMethod("hello").invoke(instance);
    }
}

输出结果:

自定义加载器加载类:customClassLoader->com.example.CustomClass
Hello, I'm Niuxiangqian

从输出结果可以看出,自定义的类加载器成功加载了自定义的类,并成功调用了其中的方法。

4. 原理和源码解析

在Java的类加载机制中,每个类加载器都有一个父类加载器。当一个类加载器需要加载某个类时,它首先会委托给父类加载器进行加载。只有当父类加载器无法加载时,子加载器才会尝试加载。这样的机制保证了类的唯一性和防止类的重复加载。

通过自定义类加载器并重写loadClass方法,我们可以打破双亲委派机制。在本例中,我们自定义的类加载器首先判断需要加载的类是否满足自定义加载条件,如果满足则自行加载,否则委托给父类加载器。这样就实现了自定义类加载的逻辑。

正文到此结束
评论插件初始化中...
Loading...