好处
双亲委派模型是 Java 类加载器机制中的一种设计思想,它将类加载操作委派给父类加载器,只有在父类加载器无法加载某个类时才由子类加载器来加载。双亲委派模型的好处包括:
隔离性:通过将类加载操作委派给父类加载器,每个类加载器都有自己的命名空间,可以确保不同类加载器加载的类彼此隔离,防止类之间的冲突和混淆。
避免重复加载:双亲委派模型可以避免同一个类被多个类加载器重复加载。当一个类被加载后,它会被缓存到父类加载器的命名空间中,后续相同的类加载请求会直接返回缓存的类,避免了重复加载和内存浪费。
安全性:通过双亲委派模型,Java 运行时环境可以确保核心类库的安全性和一致性。核心类库由启动类加载器加载,其他类都委托给父类加载器加载,从而确保了核心类库的完整性和安全性。
减少类加载器冲突:双亲委派模型使得类加载器之间的关系变得清晰和有序,可以有效地避免类加载器冲突和类重复加载的问题。每个类加载器都有自己的父类加载器,通过委派机制保证了类加载的有序性和一致性。
有哪些框架用到了双亲委派模型
Java 核心类库:Java 核心类库是由启动类加载器加载的,它们的加载遵循双亲委派模型。
Servlet 容器:例如 Tomcat、Jetty 等 Servlet 容器通常会使用双亲委派模型加载 Web 应用程序中的类和资源。
Spring 框架:Spring 框架是一个广泛使用的 Java 开发框架,它的核心模块和依赖库都会受到双亲委派模型的影响。
Hibernate ORM:Hibernate 是一个流行的对象关系映射框架,它也会利用 Java 类加载器的双亲委派模型来加载实体类和持久化对象。
Apache Commons 系列:Apache Commons 是一系列常用的 Java 工具库,例如 Apache Commons Lang、Apache Commons IO 等,它们也会使用到双亲委派模型。
JUnit 测试框架:JUnit 是一个流行的 Java 单元测试框架,它的加载过程也会受到双亲委派模型的影响。
Log4j 日志框架:Log4j 是一个常用的 Java 日志框架,它的加载过程也会遵循双亲委派模型。
如何打破双亲委派机制
双亲委派机制是一种 Java 类加载机制,它保证了 Java 类加载的一致性和安全性。在这种机制下,当一个类加载器收到类加载请求时,它会首先将请求委派给父类加载器进行加载,如果父类加载器无法完成加载任务,才会由当前类加载器进行加载。这个机制确保了 Java 核心类库(如 java.lang.Object
)不会被重写或篡改。
虽然双亲委派机制有其重要性,但在某些特殊场景下,可能需要打破这种机制。例如,当需要加载不同版本的库时,或者进行插件化开发时,可能需要自定义类加载器来打破双亲委派机制。以下是几种打破双亲委派机制的方法:
1. 自定义类加载器
通过自定义类加载器,可以在 loadClass
方法中控制类的加载顺序,从而绕过双亲委派机制。
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 先尝试自己加载
try {
byte[] classData = getClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
}
} catch (Exception e) {
// Ignore and fallback to parent
}
// 如果加载失败,委派给父类加载器
return super.loadClass(name, resolve);
}
private byte[] getClassData(String className) {
// 从文件或其他来源获取类数据
return null; // 示例中返回 null
}
}
2. 使用 Thread.getContextClassLoader()
在某些情况下,可以通过设置线程上下文类加载器来绕过双亲委派机制。线程上下文类加载器可以通过 Thread.setContextClassLoader()
来设置,并通过 Thread.getContextClassLoader()
来获取。
Thread.currentThread().setContextClassLoader(new CustomClassLoader());
3. 重写 findClass
方法
自定义类加载器时,可以重写 findClass
方法,而不是 loadClass
方法。这种方法通常在需要更细粒度控制类加载过程时使用。
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] getClassData(String className) {
// 从文件或其他来源获取类数据
return null; // 示例中返回 null
}
}
4. 使用 URLClassLoader
URLClassLoader
是一种现成的类加载器,允许从 URL 指定的路径加载类。在某些场景下,可以利用 URLClassLoader
来实现类加载的灵活性,打破双亲委派机制。
URL[] urls = {new URL("file:///path/to/classes/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls, null); // null 代表不使用父类加载器
Class<?> clazz = urlClassLoader.loadClass("com.example.MyClass");
注意事项
- 打破双亲委派机制可能会带来类加载的安全性和一致性问题,需要谨慎处理。
- 在使用自定义类加载器时,需要考虑类的重复加载、类冲突等问题。
- 尽量在插件系统、应用容器等需要隔离不同版本类库的场景下使用自定义类加载器。
通过以上方法,可以在必要时打破双亲委派机制,从而实现更灵活的类加载策略。