惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
小众软件
小众软件
S
SegmentFault 最新的问题
GbyAI
GbyAI
量子位
爱范儿
爱范儿
L
LangChain Blog
Vercel News
Vercel News
A
About on SuperTechFans
腾讯CDC
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
V2EX
V
Visual Studio Blog
美团技术团队
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
N
Netflix TechBlog - Medium

博客园 - 自由港

postgres 支持全文索引 查看 milvus 中的数据 查询数据库锁死情况 使用 keepalived 实现 tendis 高可用 部署tendis IDEA 运行 main 方法导致整个项目 install hive 基础操作 在 服务器部署 seatunnel web服务 使用 seatunnel web 设计一个数据同步 如何 运行 seatunnel web 开发版 使用 seatunnel 实现数据同步 avro 数据入门 NIFI国际化 使用 NIFI读取EXCEL 数据到数据库 NIFI 使用HTTP 作为数据源接收数据 使用NIFI 同步数据库表 使用 NIFI监控数据库表 切换JDK NIFI实现配置分发
自定义classloader
自由港 · 2025-11-17 · via 博客园 - 自由港

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是生效的。