虽然 JVM 默认使用双亲委派机制 来 保证类加载的安全性和稳定性,但在某些情况下(如 插件隔离、不同版本 JAR 并存、动态代理 等),需要 绕过或改进双亲委派。
可以通过 三种方式 来打破双亲委派:
1. 重写 findClass(),子加载器优先加载
默认的 loadClass() 机制 是先让 父加载器 尝试加载,只有当父加载器加载失败时,子类加载器才会尝试 findClass() 进行自定义加载。
如果我们希望子类加载器优先加载类,而不是先让父加载器尝试加载,可以 直接重写 loadClass() 方法,让它先查找自己加载的类,再委派给父加载器。
示例:重写 findClass(),子加载器优先
java
import java.io.*;
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String className) {
String filePath = classPath + className.replace(".", "/") + ".class";
try (InputStream input = new FileInputStream(filePath);
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
return output.toByteArray();
} catch (IOException e) {
return null;
}
}
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// **打破双亲委派**,先尝试自己加载
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
clazz = findClass(name); // **优先使用自己的 `findClass()`**
} catch (ClassNotFoundException e) {
clazz = super.loadClass(name, false); // **如果找不到,才交给父类**
}
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
public static void main(String[] args) throws Exception {
CustomClassLoader loader = new CustomClassLoader("D:/custom_classes/");
Class<?> clazz = loader.loadClass("com.example.MyClass");
Object obj = clazz.newInstance();
System.out.println("类加载器:" + obj.getClass().getClassLoader());
}
}
说明:
先检查 自己是否已经加载了类(findLoadedClass())。
自己优先加载(调用 findClass())。
如果 找不到,再交给父类加载器。
这样,com.example.MyClass 不会被父类加载器加载,而是由自定义类加载器加载。
2. 通过自定义 ClassLoader 实现模块隔离
场景:
在 插件化开发(Plugin System) 或 J2EE 服务器 中,不同模块可能包含 相同类的不同版本,但由于 双亲委派机制,默认的类加载器会导致 类冲突。
解决方案:
为每个插件或 Web 应用创建一个独立的类加载器,这样不同插件可以加载自己的 JAR 包,避免冲突。
示例:插件化 ClassLoader
java
import java.net.URL;
import java.net.URLClassLoader;
public class PluginClassLoader extends URLClassLoader {
public PluginClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 先查找自己加载的类
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
clazz = findClass(name); // 先从插件目录加载
} catch (ClassNotFoundException e) {
clazz = super.loadClass(name); // 如果找不到,才交给父类
}
}
return clazz;
}
}
使用示例
java
public class PluginLoaderTest {
public static void main(String[] args) throws Exception {
URL jarUrl = new URL("file:D:/plugins/pluginA.jar");
PluginClassLoader pluginLoader = new PluginClassLoader(new URL[]{jarUrl}, ClassLoader.getSystemClassLoader());
Class<?> pluginClass = pluginLoader.loadClass("com.plugin.PluginMain");
Object pluginInstance = pluginClass.newInstance();
System.out.println("插件类加载器:" + pluginInstance.getClass().getClassLoader());
}
}
效果:
每个插件都可以加载自己的 JAR,而不会影响主程序。
避免 JAR 冲突,支持不同版本共存。
3. 动态调整 ThreadContextClassLoader,解决 AOP/代理类加载问题
场景:
在 AOP(如 Spring 代理)、动态代理(Proxy)、反射 中,代理类 和 被代理类 可能由不同的类加载器加载,导致 ClassCastException。
解决方案:临时切换线程上下文类加载器 (ThreadContextClassLoader)。
示例:动态调整 ThreadContextClassLoader
public class ThreadContextClassLoaderDemo {
public static void main(String[] args) {
ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
try {
ClassLoader customLoader = new CustomClassLoader("D:/custom_classes/");
Thread.currentThread().setContextClassLoader(customLoader);
// 使用反射加载类
Class<?> clazz = Class.forName("com.example.DynamicClass", true, customLoader);
Object obj = clazz.newInstance();
System.out.println("动态加载类:" + obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 还原原来的类加载器
Thread.currentThread().setContextClassLoader(originalLoader);
}
}
}
效果:
动态切换类加载器,保证代理类和被代理类使用相同的 ClassLoader,避免 ClassCastException。
Spring AOP、JNDI、Hibernate 也常用这个方法 解决 ClassLoader 不匹配问题。
方法 场景 解决方案**