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

完整的自定义类加载器案例
基础的自定义类加载器
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;
}
}
-
核心方法:
findClass():自定义加载逻辑的主要方法defineClass():将字节码转换为Class对象loadClass():加载入口,遵循双亲委派模型
-
特点:
- 类加载器隔离
- 支持加密/解密
- 支持热部署
- 支持从不同来源加载(文件、网络、数据库等)
-
应用场景:
- Web容器(Tomcat)
- OSGI框架
- 插件系统
- 代码热更新
- 类文件加密保护
-
注意事项:
- 注意双亲委派模型的遵循和打破
- 注意类的唯一性(同一类由不同加载器加载是不同的类)
- 注意内存泄漏问题
- 注意安全性和权限控制