自定义类加载器案例

wen java案例 3

我来给你一个完整的自定义类加载器案例,包含多个实际场景的演示。

自定义类加载器案例

完整的自定义类加载器案例

基础的自定义类加载器

import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
 * 从指定目录加载类的自定义类加载器
 */
public class FileClassLoader extends ClassLoader {
    // 类文件存放的目录
    private String classpath;
    // 缓存已加载的类
    private Map<String, Class<?>> classCache = new HashMap<>();
    public FileClassLoader(String classpath) {
        // 设置为系统类加载器的父加载器
        super(ClassLoader.getSystemClassLoader());
        this.classpath = classpath;
    }
    /**
     * 重写 findClass 方法,实现自己的加载逻辑
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 先从缓存中获取
        if (classCache.containsKey(name)) {
            return classCache.get(name);
        }
        // 将类名转换为文件路径
        String path = classpath + File.separator + 
                     name.replace('.', File.separatorChar) + ".class";
        File file = new File(path);
        if (!file.exists()) {
            throw new ClassNotFoundException(name);
        }
        try {
            // 读取字节码
            byte[] classData = readClassFile(file);
            // 调用 defineClass 方法将字节码转换为 Class 对象
            Class<?> clazz = defineClass(name, classData, 0, classData.length);
            // 放入缓存
            classCache.put(name, clazz);
            return clazz;
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    /**
     * 读取类文件的字节码
     */
    private byte[] readClassFile(File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, length);
            }
            return bos.toByteArray();
        }
    }
}

加密/解密类加载器

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.Key;
/**
 * 支持对class文件进行加密和解密的类加载器
 */
public class EncryptedClassLoader extends ClassLoader {
    private String classpath;
    private String key;
    public EncryptedClassLoader(String classpath, String key, ClassLoader parent) {
        super(parent);
        this.classpath = classpath;
        this.key = key;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 读取加密的class文件
            byte[] encryptedData = readEncryptedClass(name);
            // 解密class文件
            byte[] decryptedData = decrypt(encryptedData);
            // 加载类
            return defineClass(name, decryptedData, 0, decryptedData.length);
        } catch (Exception e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    /**
     * 读取加密的class文件
     */
    private byte[] readEncryptedClass(String className) throws IOException {
        String fileName = className.replace('.', '/') + ".class";
        File file = new File(classpath, fileName);
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, length);
            }
            return bos.toByteArray();
        }
    }
    /**
     * 解密class文件(使用简单的XOR加密示例)
     */
    private byte[] decrypt(byte[] data) {
        byte[] result = new byte[data.length];
        byte[] keyBytes = key.getBytes();
        for (int i = 0; i < data.length; i++) {
            result[i] = (byte) (data[i] ^ keyBytes[i % keyBytes.length]);
        }
        return result;
    }
    /**
     * 加密工具方法(用于生成加密的class文件)
     */
    public static void encryptFile(String sourcePath, String targetPath, String key) 
            throws IOException {
        byte[] keyBytes = key.getBytes();
        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(targetPath)) {
            byte[] buffer = new byte[1024];
            int length;
            int offset = 0;
            while ((length = fis.read(buffer)) != -1) {
                for (int i = 0; i < length; i++) {
                    buffer[i] = (byte) (buffer[i] ^ keyBytes[(offset + i) % keyBytes.length]);
                }
                fos.write(buffer, 0, length);
                offset += length;
            }
        }
    }
}

热部署类加载器

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
 * 支持热部署的类加载器
 */
public class HotDeployClassLoader extends ClassLoader {
    // 需要热加载的class文件路径
    private String hotClasspath;
    // 记录文件的修改时间
    private Map<String, Long> fileTimestamps = new ConcurrentHashMap<>();
    public HotDeployClassLoader(String hotClasspath, ClassLoader parent) {
        super(parent);
        this.hotClasspath = hotClasspath;
    }
    /**
     * 尝试加载类(检查是否需要重新加载)
     */
    public Class<?> loadClassForHotDeploy(String name) throws ClassNotFoundException {
        // 检查文件是否被修改
        if (isClassModified(name)) {
            // 如果被修改,创建一个新的类加载器来加载
            return new FileClassLoader(hotClasspath).loadClass(name);
        } else {
            // 没有修改,使用当前加载器加载
            return loadClass(name);
        }
    }
    /**
     * 检查类文件是否被修改
     */
    private boolean isClassModified(String className) {
        String path = hotClasspath + File.separator + 
                     className.replace('.', File.separatorChar) + ".class";
        File file = new File(path);
        if (!file.exists()) {
            return false;
        }
        long lastModified = file.lastModified();
        Long prevModified = fileTimestamps.get(className);
        if (prevModified == null || prevModified != lastModified) {
            fileTimestamps.put(className, lastModified);
            return true;
        }
        return false;
    }
    /**
     * 从Jar文件加载类
     */
    public Class<?> loadClassFromJar(String jarPath, String className) 
            throws ClassNotFoundException, IOException {
        try (JarFile jarFile = new JarFile(jarPath)) {
            String entryName = className.replace('.', '/') + ".class";
            JarEntry entry = jarFile.getJarEntry(entryName);
            if (entry == null) {
                throw new ClassNotFoundException(className);
            }
            try (InputStream is = jarFile.getInputStream(entry)) {
                byte[] classBytes = is.readAllBytes();
                return defineClass(className, classBytes, 0, classBytes.length);
            }
        }
    }
}

网络类加载器

import java.io.*;
import java.net.*;
/**
 * 从网络加载类的类加载器
 */
public class NetworkClassLoader extends ClassLoader {
    private String serverUrl;
    private Map<String, Class<?>> classCache = new HashMap<>();
    public NetworkClassLoader(String serverUrl, ClassLoader parent) {
        super(parent);
        this.serverUrl = serverUrl;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 先从缓存获取
        if (classCache.containsKey(name)) {
            return classCache.get(name);
        }
        try {
            // 从网络下载class文件
            byte[] classData = downloadClass(name);
            // 将字节码转换为Class对象
            Class<?> clazz = defineClass(name, classData, 0, classData.length);
            classCache.put(name, clazz);
            return clazz;
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    /**
     * 从服务器下载class文件
     */
    private byte[] downloadClass(String className) throws IOException {
        String path = className.replace('.', '/') + ".class";
        URL url = new URL(serverUrl + "/" + path);
        try (InputStream is = url.openStream();
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) != -1) {
                bos.write(buffer, 0, length);
            }
            return bos.toByteArray();
        }
    }
}

使用示例和测试

import java.lang.reflect.Method;
/**
 * 测试自定义类加载器
 */
public class ClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建测试类文件
        createTestClassFile();
        // 2. 使用文件类加载器
        System.out.println("=== 文件类加载器测试 ===");
        FileClassLoader fileLoader = new FileClassLoader("hot-classes");
        Class<?> clazz1 = fileLoader.loadClass("com.example.Person");
        Object obj1 = clazz1.getDeclaredConstructor().newInstance();
        Method setNameMethod = clazz1.getMethod("setName", String.class);
        setNameMethod.invoke(obj1, "张三");
        Method getNameMethod = clazz1.getMethod("getName");
        System.out.println("姓名: " + getNameMethod.invoke(obj1));
        System.out.println("类加载器: " + clazz1.getClassLoader().getClass().getName());
        // 3. 测试加密类加载器
        System.out.println("\n=== 加密类加载器测试 ===");
        String key = "MySecretKey2024";
        // 加密原始class文件
        EncryptedClassLoader.encryptFile(
            "hot-classes/com/example/Person.class",
            "hot-classes/com/example/Person.class.enc",
            key
        );
        // 使用加密类加载器加载
        EncryptedClassLoader encryptedLoader = 
            new EncryptedClassLoader("hot-classes", key, 
                                   ClassLoader.getSystemClassLoader());
        try {
            Class<?> clazz2 = encryptedLoader.loadClass("com.example.Person");
            System.out.println("加密类加载成功: " + clazz2.getName());
        } catch (ClassNotFoundException e) {
            System.out.println("加密类加载失败: " + e.getMessage());
        }
        // 4. 测试热部署
        System.out.println("\n=== 热部署测试 ===");
        HotDeployClassLoader hotLoader = 
            new HotDeployClassLoader("hot-classes", 
                                   ClassLoader.getSystemClassLoader());
        // 第一次加载
        Class<?> hotClazz1 = hotLoader.loadClassForHotDeploy("com.example.Person");
        System.out.println("第一次加载: " + hotClazz1.getClassLoader().getClass().getName());
        // 修改class文件(模拟更新)
        Thread.sleep(100);
        modifyClassFile();
        // 第二次加载
        Class<?> hotClazz2 = hotLoader.loadClassForHotDeploy("com.example.Person");
        System.out.println("第二次加载: " + hotClazz2.getClassLoader().getClass().getName());
        System.out.println("两个类是同一个吗? " + (hotClazz1 == hotClazz2));
    }
    /**
     * 创建测试类文件
     */
    private static void createTestClassFile() throws IOException {
        String source = 
            "package com.example;\n" +
            "public class Person {\n" +
            "    private String name;\n" +
            "    \n" +
            "    public Person() {\n" +
            "        this.name = \"默认名字\";\n" +
            "    }\n" +
            "    \n" +
            "    public void setName(String name) {\n" +
            "        this.name = name;\n" +
            "    }\n" +
            "    \n" +
            "    public String getName() {\n" +
            "        return name;\n" +
            "    }\n" +
            "}";
        File sourceFile = new File("hot-classes/com/example/Person.java");
        sourceFile.getParentFile().mkdirs();
        try (java.io.FileWriter writer = new java.io.FileWriter(sourceFile)) {
            writer.write(source);
        }
        // 编译Java文件
        try {
            javax.tools.JavaCompiler compiler = 
                javax.tools.ToolProvider.getSystemJavaCompiler();
            int result = compiler.run(null, null, null, 
                                    sourceFile.getAbsolutePath(),
                                    "-d", "hot-classes");
            if (result != 0) {
                System.out.println("编译失败");
            }
        } catch (Exception e) {
            System.out.println("编译错误: " + e.getMessage());
        }
    }
    /**
     * 修改class文件(用于测试热部署)
     */
    private static void modifyClassFile() throws IOException, InterruptedException {
        // 简化实现:重新编译修改过的方法
        String source = 
            "package com.example;\n" +
            "public class Person {\n" +
            "    private String name;\n" +
            "    private int age;\n" +  // 新增字段
            "    \n" +
            "    public Person() {\n" +
            "        this.name = \"默认名字\";\n" +
            "        this.age = 18;\n" +
            "    }\n" +
            "    \n" +
            "    public void setName(String name) {\n" +
            "        this.name = name;\n" +
            "    }\n" +
            "    \n" +
            "    public String getName() {\n" +
            "        return name;\n" +
            "    }\n" +
            "    \n" +
            "    public int getAge() {\n" +  // 新增方法
            "        return age;\n" +
            "    }\n" +
            "}";
        File sourceFile = new File("hot-classes/com/example/Person.java");
        try (java.io.FileWriter writer = new java.io.FileWriter(sourceFile)) {
            writer.write(source);
        }
        javax.tools.JavaCompiler compiler = 
            javax.tools.ToolProvider.getSystemJavaCompiler();
        compiler.run(null, null, null, 
                    sourceFile.getAbsolutePath(),
                    "-d", "hot-classes");
    }
}

实际应用场景示例

/**
 * Tomcat风格的Web应用类加载器
 */
public class WebAppClassLoader extends ClassLoader {
    private String webAppPath;  // Web应用根目录
    private Map<String, byte[]> classByteCache = new HashMap<>();
    public WebAppClassLoader(String webAppPath, ClassLoader parent) {
        super(parent);
        this.webAppPath = webAppPath;
    }
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        // 1. 检查是否已经加载
        synchronized (getClassLoadingLock(name)) {
            Class<?> clazz = findLoadedClass(name);
            if (clazz == null) {
                // 2. 先尝试从Web应用自己加载
                try {
                    clazz = findClass(name);
                } catch (ClassNotFoundException e) {
                    // 3. 如果自己的加载失败,委托给父加载器
                    clazz = super.loadClass(name, false);
                }
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从WEB-INF/classes下加载
        String relativePath = name.replace('.', '/') + ".class";
        File classFile = new File(webAppPath, "WEB-INF/classes/" + relativePath);
        // 如果不存在,尝试从WEB-INF/lib的jar包中加载
        if (!classFile.exists()) {
            classFile = findInJars(name);
        }
        if (classFile == null) {
            throw new ClassNotFoundException(name);
        }
        try {
            byte[] classData = Files.readAllBytes(classFile.toPath());
            return defineClass(name, classData, 0, classData.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    private File findInJars(String className) {
        File libDir = new File(webAppPath, "WEB-INF/lib");
        File[] jars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
        if (jars != null) {
            for (File jar : jars) {
                try (JarFile jarFile = new JarFile(jar)) {
                    String entryName = className.replace('.', '/') + ".class";
                    // 检查是否存在并提取内容(简化处理)
                    // 实际实现时应该返回类数据
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}
  1. 核心方法

    • findClass():自定义加载逻辑的主要方法
    • defineClass():将字节码转换为Class对象
    • loadClass():加载入口,遵循双亲委派模型
  2. 特点

    • 类加载器隔离
    • 支持加密/解密
    • 支持热部署
    • 支持从不同来源加载(文件、网络、数据库等)
  3. 应用场景

    • Web容器(Tomcat)
    • OSGI框架
    • 插件系统
    • 代码热更新
    • 类文件加密保护
  4. 注意事项

    • 注意双亲委派模型的遵循和打破
    • 注意类的唯一性(同一类由不同加载器加载是不同的类)
    • 注意内存泄漏问题
    • 注意安全性和权限控制

抱歉,评论功能暂时关闭!