1.概述

classloader 的作用是加载字节码到jvm,有些情况下,我们比如使用插件模式,可能需要自定义从外部加载插件到jvm。这里还有一个需要注意的是java的双亲委派机制。

2.实现过程

2.1.定义自定义classloader

package com.example.demo.loader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        // 将包名转换为路径
        String fileName = classPath + File.separator +
                className.replace('.', File.separatorChar) + ".class";
        try (InputStream is = new FileInputStream(fileName);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            int ch;
            while ((ch = is.read()) != -1) {
                baos.write(ch);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

这个代码继承 classloader,并重写了findClass.

2.2.定义外部类

package com.example.demo.loader;

public class MyDynamicClass {
    public void sayHello() {
        System.out.println("Hello from dynamically loaded class!");
    }
}

2.3. 使用自定义classloader

package com.example.demo.loader;

public class ClassLoaderDemo {

    public static void main(String[] args) {
        try {
            // 指向 .class 文件的根目录
            MyClassLoader loader = new MyClassLoader("D:\\work\\research\\demo\\target\\classes");

            // 加载类(注意使用全限定名)
            Class<?> clazz = loader.loadClass("com.example.demo.loader.MyDynamicClass");

            // 创建实例
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // 调用方法
            clazz.getMethod("sayHello").invoke(instance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样我们可以看到 MyDynamicClass 的输出。
Hello from dynamically loaded class
这个时候,如果我将这个 MyDynamicClass 拷贝到D:\temp\class 目录下。

我们更改代码:
MyClassLoader loader = new MyClassLoader("D:\temp\class");
这个时候,我在修改一下MyDynamicClass 的输出

package com.example.demo.loader;

public class MyDynamicClass {
    public void sayHello() {
        System.out.println("测试一下输出!");
    }
}

这个时候在ClassLoaderDemo执行一下,我们发现输出是"测试一下输出!",并没有使用D:\temp\class 下的class

这里涉及到一个 java的双亲委派机制。

2.3 什么是双亲委派机制

双亲委派机制是Java类加载器的一种工作模式,当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委托给父类加载器去完成,依次向上递归,只有当父类加载器无法完成加载时,子加载器才会尝试自己加载。

2.4 如果不想使用双亲机制

我们的类可以这么修改一下

Class<?> clazz = loader.findClass("com.example.demo.loader.MyDynamicClass");

这个就是没有走 loadClass 方法,直接调用 findClass方法,获取到外部类。

2.5 双亲委派机制的实现原理代码

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先,检查请求的类是否已经被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    // 如果父加载器不为空,则委托给父加载器加载
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父加载器为空,则委托给启动类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父加载器无法加载,抛出ClassNotFoundException
            }

            if (c == null) {
                // 如果父加载器无法加载,则调用自己的findClass方法进行加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

从这个代码我们就可以看出,因为 在classpath下已经找到了 实现类,所以就不会在使用自定义classload加载的类。
如果直接从class path 下删除哪个 MyDynamicClass class 文件,我们可以看到自定义 classloader是生效的。

posted on 2025-11-17 08:52  自由港  阅读(3)  评论(0)    收藏  举报