1、什么是双亲委派机制
双亲委派机制(Parent Delegation Mechanism)是Java中的一种类加载机制。在Java中,类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。
这种机制的设计目的是为了保证类的加载是有序的,避免重复加载同一个类。Java中的类加载器形成了一个层次结构,根加载器(Bootstrap ClassLoader)位于最顶层,它负责加载Java核心类库。其他加载器如扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)都有各自的加载范围和职责。通过双亲委派机制,可以确保类在被加载时,先从上层的加载器开始查找,逐级向下,直到找到所需的类或者无法找到为止。
这种机制的好处是可以避免类的重复加载,提高了类加载的效率和安全性。同时,它也为Java提供了一种扩展机制,允许开发人员自定义类加载器,实现特定的加载策略。
另外:
推荐一个程序员免费学习的编程网站:我爱编程网(www.love-coding.com)
涵盖 Java几乎覆盖了所有主流技术面试题,还有市面上最全的技术精品系列教程,免费提供。
2、双亲委派机制的优缺点
优点:
- 避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。
- 安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。
- 扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。
缺点:
- 灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。
- 破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。
- 不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外的处理。
总体而言,双亲委派机制通过层次结构和委派机制提供了一种有序、安全的类加载方式,但也存在一些限制和不适用的情况。
3、双亲委派机制的应用场景
双亲委派机制在Java中有多种应用场景。一些常见的应用场景包括:
- 类加载:双亲委派机制主要用于Java中的类加载。它确保类以层次结构方式加载,从引导类加载器开始,然后是扩展类加载器,最后是应用程序类加载器。这样可以高效且一致地加载类,跨类加载器层次结构。
- 安全性:双亲委派机制在确保Java应用程序的安全性方面起着重要作用。通过委派类加载给父加载器,它可以防止未经授权或恶意代码的加载和执行。这有助于维护Java运行时环境的完整性和安全性。
- 类库:双亲委派机制对于加载和管理类库非常有用。引导类加载器位于层次结构的顶部,负责加载核心Java类。扩展类加载器负责从Java扩展目录加载类。应用程序类加载器负责从应用程序的类路径加载类。这种分离允许高效组织和管理类库。
- 自定义类加载:开发人员可以利用双亲委派机制实现具有特定加载行为的自定义类加载器。通过扩展现有的类加载器并重写类加载过程,开发人员可以引入自定义逻辑,从非标准位置加载类或对加载的类应用自定义转换。
双亲委派机制在Java中广泛应用于类加载、安全性强制执行和类库管理等方面,为Java应用程序提供了结构化和安全的环境。
4、双亲委派机制的原理
双亲委派机制是Java中的一种类加载机制。其原理如下:
- 当Java程序需要加载一个类时,首先会委托给当前类加载器的父类加载器进行加载。
- 父类加载器会按照相同的方式尝试加载该类。如果父类加载器能够成功加载该类,则加载过程结束。
- 如果父类加载器无法加载该类,则会将加载请求再次委托给它的父类加载器,直到达到顶层的引导类加载器。
- 引导类加载器是Java虚拟机内置的类加载器,它负责加载核心类库,如java.lang包下的类。
- 如果引导类加载器也无法加载该类,则会回到初始的类加载器,尝试使用自身的加载机制加载该类。
- 如果自身的加载机制仍然无法加载该类,则会抛出ClassNotFoundException异常。
通过这种双亲委派的机制,Java实现了类加载的层次结构。它可以确保类的加载是有序的,避免了重复加载和类的冲突。同时,它也提供了一种安全机制,防止恶意代码的加载和执行。
5、通过代码讲解双亲委派机制
首先,我们需要自定义一个类加载器,继承自ClassLoader类,并重写loadClass()方法。在loadClass()方法中,我们可以实现自己的类加载逻辑。
package com.pany.camp.classloader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @description: 双亲委派机制
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-30 8:41
*/
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 首先尝试使用父类加载器加载类
try {
return super.loadClass(name);
} catch (ClassNotFoundException e) {
// 如果父类加载器无法加载类,则自己加载类
return findClass(name);
}
}
@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 name) {
// 在这里实现加载类文件的逻辑,返回类文件的字节数组
// 这里只是一个示例,实际实现需要根据具体需求进行处理
try {
FileInputStream fis = new FileInputStream(name + ".class");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
fis.close();
bos.close();
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
// 创建自定义类加载器
MyClassLoader classLoader = new MyClassLoader();
try {
// 使用自定义类加载器
Class<?> clazz = classLoader.loadClass("com.pany.camp.classloader.MyClassLoader");
System.out.println("Class loaded: " + clazz.getName());
} catch (ClassNotFoundException e) {
System.out.println("Class not found");
}
}
}
我们创建了一个自定义的类加载器MyClassLoader,并使用它来加载名为"com.example.MyClass"的类。在loadClass()方法中,我们首先尝试使用父类加载器加载类,如果父类加载器无法加载类,则自己加载类。
通过运行上面代码,我们可以观察到双亲委派机制的工作原理。首先,自定义类加载器会尝试委托给父类加载器加载类,如果父类加载器能够成功加载类,则加载过程结束。如果父类加载器无法加载类,则会回到自己的加载机制,尝试使用自身的类加载逻辑加载类。
6、打破双亲委派机制
首先,我们需要自定义一个类加载器,继承自ClassLoader类,并重写loadClass()方法。在loadClass()方法中,我们可以实现自己的类加载逻辑。
6.1 为什么要打破双亲委派机制吗?
打破双亲委派机制,就像是在Java世界的稳固城墙上敲开一扇门,让新鲜空气和新思想涌入。这一机制的打破,通常出于以下几个原因:
1. 热部署的梦想
想象一下,你正在开发一个大型软件系统,每次更新都需要重启整个应用,这不仅耗时耗力,还可能导致服务中断。为了让系统能够不停机更新,开发者们梦想着实现类的热部署——在运行时动态加载和替换类,而不需要重启应用。双亲委派机制像一位守旧的家长,坚持类的加载只能发生一次。为了打破这一限制,开发者们决定自定义类加载器,让类的热替换成为可能。
2. 非标准类文件的探险
在Java的广阔世界中,总有一些探险家想要探索未知的领域。他们遇到了一些特殊的类文件,比如动态生成的字节码或非标准的类文件格式。这些文件像是外星语言,标准的类加载器无法理解。为了解码这些神秘的信息,开发者们决定打破双亲委派机制,自定义类加载器,以实现对这些非标准类文件的加载和解析。
3. 类加载动态控制的魔法
还有一些开发者,他们像是掌握了魔法的魔法师,需要对类的加载进行特殊的控制。他们可能需要对特定的类进行加密、解密或验证等操作。这些特殊的需求,双亲委派机制无法满足。因此,他们决定打破这一机制,自定义类加载器,在加载类时施展他们的魔法。
4. 框架和容器的独立性
在JavaEE容器(如Tomcat、WebLogic等)中,通常会使用自定义的类加载器来加载应用程序的类,以实现应用程序的隔离和独立性。这些容器需要打破双亲委派机制,使用自定义的类加载器来加载应用程序的类,确保不同应用之间的类不会相互干扰。
虽然打破双亲委派机制可以带来上述的好处,但它也可能引入一些潜在的风险和问题。类的冲突、不一致性等问题可能会像不请自来的客人,给Java世界带来混乱。因此,当开发者们决定打破这一机制时,他们需要谨慎考虑,确保自定义的类加载器能够正确处理类的加载和依赖关系,维护Java世界的和谐与秩序。
6.2 怎样打破双亲委派机制?
在Java中,列举了几种方法可以打破双亲委派机制:
1.自定义类加载器
当两个类的全限定名相同时,就不可以同时加载,入tomcat运行两个web服务时就会出现冲突(相同类名),不够tomcat通过为每一个应用分配一个应用类加载器,打破双亲委派机制。
自定义实现(1)类继承ClassLoader类,重写loadClass方法(2)loadClass中获取被加载类数据(loadClassData)(3)调用defineClass完成类加载注:加载类的时候会要求所有类都继承父类(默认object类),不然会报错,可以将待加载类同目录放入父类,也可直接将父类交由ClassLoader中的类加载器加载,在一个Java虚拟机中,只有同一个类加载器外加相同的类限定名才会认为冲突
2.线程上下文类加载器
通过Thread类的setContextClassLoader()方法,可以设置线程的上下文类加载器。在某些框架或库中,会使用线程上下文类加载器来加载特定的类,从而打破双亲委派机制。
3.Java SPI机制
Java SPI(Service Provider Interface)是一种标准的服务发现机制,在SPI中,服务的实现类通过在META-INF/services目录下的配置文件中声明,而不是通过类路径来查找。通过SPI机制,可以实现在不同的类加载器中加载不同的服务实现类,从而打破双亲委派机制。
4.osgi框架类加载器(不再使用)
自己实现了一套加载机制,实现了类的热部署,现在可用arthas实线终:打破双亲委派机制的唯一方式就是重写ClassLoader。
总结
双亲委派机制是Java类加载器的一种工作机制,它的核心思想是在类加载的过程中,优先将加载请求委派给父类加载器,只有在父类加载器无法加载时,才由子类加载器尝试加载。
双亲委派机制的主要特点和优势包括:
- 避免类的重复加载:当一个类被加载后,它会被父类加载器缓存起来,避免了重复加载同一个类的问题,提高了类加载的效率。
- 类的隔离和安全性:通过双亲委派机制,不同的类加载器加载的类具有不同的命名空间,相同类名的类可以被不同的类加载器加载,实现了类的隔离和安全性。
- 保护核心类库的完整性:核心类库由启动类加载器加载,避免了用户自定义的类替换核心类库的情况,保护了核心类库的完整性。
总结起来:
- 双亲委派机制通过层级结构的类加载器组织,实现了类的共享、隔离和安全性。
- 它是Java类加载器的一种重要机制,为Java应用程序提供了良好的类加载环境。
- 然而,在某些特定的场景下,为了满足特定的需求,可能需要打破双亲委派机制,使用自定义的类加载器来加载类。
- 在使用自定义类加载器时,需要仔细评估和测试,确保能够正确处理类的加载和依赖关系。