[JAVA] Java面试宝典:什么是Java中的双亲委派模型?

652 0
Honkers 2025-3-5 16:55:01 | 显示全部楼层 |阅读模式

Java面试宝典专栏范围:JAVA基础,面向对象编程(OOP),异常处理,集合框架,Java I/O,多线程编程,设计模式,网络编程,框架和工具等全方位面试题详解
每日更新Java面试宝典专栏:Java面试宝典
感兴趣的可以先收藏起来,大家在遇到JAVA面试题等相关问题都可以给我留言咨询,希望帮助更多的人

回答重点

双亲委派模型是Java类加载机制的设计模式之一 。它的核心思想是:类加载器在加载某个类时,会先委派给父类加载器去加载,父类加载器无法加载时,才由当前类加载器自行去加载。

工作流程:

  1. 当前类加载器收到一个类加载的请求
  2. 当前类加载器会将这个请求委托给它的父类加载器去加载
  3. 父类加载器再将请求向上继续委派,直到达到Bootstrap类加载器
  4. 如果Bootstrap类加载器无法加载目标类(即不在其加载范围内),加载请求回到下一级(即扩展类加载器),由它尝试加载类
  5. 如果到达当前类加载器仍然无法加载,则抛ClassNotFountExeption异常

详细解释

三种类加载器

Java 中的类加载器分为以下三类:

  1. 启动类加载器(Bootstrap ClassLoader)

    • 职责:负责加载 JVM 的核心类库。
    • 加载范围:
      • /lib 目录,例如 rt.jar。
      • 被 -Xbootclasspath 指定的路径下的类。
    • 实现方式:由 C++ 实现,是 JVM 的一部分。
  2. 扩展类加载器(Extension ClassLoader)

    • 职责:加载 JVM 扩展功能的类库。
    • 加载范围:
      • /lib/ext 目录。
      • 系统属性 java.ext.dirs 指定的路径下的类。
    • 实现方式:由 java.lang.ClassLoader 继承实现。
  3. 应用程序类加载器(Application ClassLoader)

    • 职责:加载用户定义类路径(classpath)下的类。
    • 加载范围:
      • 开发者编写的类和资源文件。
    • 特性:是 Java 中默认的类加载器。若未自定义类加载器,则所有类将由此加载。

为什么要有双亲委派机制?

  1. 避免类的重复加载: 确保同一个类不会被重复加载。当某个类已经被加载到内存中时(例如基础类库中的java.lang.Object,或系统中已有的一些核心类),其他类加载器如果再想加载该类,就会通过双亲委派机制交给已有的类加载器处理,避免重复加载。

  2. 提高安全性: 通过将核心类交由顶层类加载器(如Bootstrap ClassLoader)加载,防止核心API被篡改。这样任何用户自定义的类加载器都无法替代Java的核心类,保护了运行时环境的完整性和安全性。

  3. 保证一致性: Java提供的核心类库(Java API)在任何环境下都应该是一致的,通过双亲委派机制,确保了无论在什么自定义类加载器下,核心类库总是由系统的父类加载器来加载的,这保证了Java应用程序在不同环境下的一致行为。

  4. 简化系统架构: 双亲委派模型简化了Java虚拟机对类及其依赖的管理,使类加载器之间形成一种树状结构,使得系统架构更为简明,减少了类加载器之间的复杂交互。

那你知道有违反双亲委派的例子吗?

典型违反双亲委派的例子就是JDBC
JDBC 的接口是类库定义的,但实现在各大数据库厂商提供的 jar 包中,通过启动类加载器找不到这个实现类,所以需要应用程序加载器完成这个任务,这就违反了自下而上的委托机制。

具体做法是搞了个线程上下文类加载器,通过 setContextClassLoader () 默认设置了应用程序类加载器,然后通过 Thread.current.currentThread ().getContextClassLoader () 获得类加载器来加载。

这是一个具体的例子,实际上 Java 的 SPI 机制都违反了双亲委派模型。因为 SPI 允许开发者在类路径中自定义服务实现,通常通过线程上下文类加载器来加载 SPI 实现类,绕过了父类加载器。

除此之外,在 Java EE 容器(如 Tomcat、WebLogic)中,每个 Web 应用有自己的类加载器,应用级别的类加载器优先加载应用的类库,而不是父类加载器。所以它们也违反了双亲委派。

请你自定义一个类加载器?

继承ClassLoader类冲洗额findClass方法即可实现

  1. import java.io.*;
  2. public class CustomClassLoader extends ClassLoader {
  3. private String classPath;
  4. // 构造方法,传入类文件路径
  5. public CustomClassLoader(String classPath) {
  6. this.classPath = classPath;
  7. }
  8. // 重写 findClass 方法
  9. @Override
  10. protected Class<!--?--> findClass(String name) throws ClassNotFoundException {
  11. try {
  12. // 将类名转换为路径
  13. String fileName = classPath + name.replace('.', '/') + ".class";
  14. // 读取类文件
  15. byte[] classData = loadClassData(fileName);
  16. if (classData == null) {
  17. throw new ClassNotFoundException("Class file for " + name + " not found");
  18. }
  19. // 将字节数组转换为 Class 对象
  20. return defineClass(name, classData, 0, classData.length);
  21. } catch (IOException e) {
  22. throw new ClassNotFoundException("Error reading class file for " + name, e);
  23. }
  24. }
  25. // 加载类文件为字节数组
  26. private byte[] loadClassData(String fileName) throws IOException {
  27. File file = new File(fileName);
  28. if (!file.exists()) {
  29. return null;
  30. }
  31. try (InputStream inputStream = new FileInputStream(file);
  32. ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
  33. byte[] buffer = new byte[1024];
  34. int bytesRead;
  35. while ((bytesRead = inputStream.read(buffer)) != -1) {
  36. outputStream.write(buffer, 0, bytesRead);
  37. }
  38. return outputStream.toByteArray();
  39. }
  40. }
  41. public static void main(String[] args) {
  42. try {
  43. // 指定 .class 文件的路径
  44. String classPath = "/path/to/classes/";
  45. // 创建自定义类加载器
  46. CustomClassLoader customClassLoader = new CustomClassLoader(classPath);
  47. // 类的全限定名,例如 "com.example.MyClass"
  48. String className = "com.example.MyClass";
  49. // 使用自定义类加载器加载类
  50. Class<!--?--> loadedClass = customClassLoader.loadClass(className);
  51. // 打印加载的类信息
  52. System.out.println("Class loaded: " + loadedClass.getName());
  53. System.out.println("Class loader: " + loadedClass.getClassLoader());
  54. // 调用类的默认构造方法创建一个实例
  55. Object instance = loadedClass.getDeclaredConstructor().newInstance();
  56. System.out.println("Instance created: " + instance);
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
复制代码

这这段自定义类加载器代码实现了一个能够从指定路径加载 .class 文件的功能,通过覆写 ClassLoader 的 findClass 方法,动态读取类文件并将字节数据转换为 Java 的 Class 对象。构造函数接受类路径,findClass 方法处理类名转路径的转换并加载数据,而 loadClassData 方法则负责读取文件内容。主方法演示了如何使用该类加载器加载并实例化特定类,此设计适用于动态扩展应用功能及处理特殊环境下的类加载需求。

双亲委派机制先自下而上委托,再自上而下加载,那为什么不直接自上而下加载?

双亲委派机制的自上而下加载策略旨在维护Java类加载的安全性和一致性,通过优先委托给父类加载器先加载类,避免了同名类在多个加载器中引发的冲突与不一致性,确保系统核心类(如 java.lang 包)不被恶意修改,从而有效防止类篡改和安全漏洞。此外,该机制通过重用父加载器已加载的类,提高了性能,遵循单一职责原则,从而降低了系统的复杂性与耦合性。这一设计不仅增强了Java运行时环境的稳定性,也为开发者提供了一个更安全可靠的类加载框架。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4008
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行