双亲委派模型
双亲委派模型介绍
类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?这就需要提到双亲委派模型了。
根据官网介绍:
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.
翻译过来大概的意思是:
ClassLoader
类使用委托模型来搜索类和资源。每个ClassLoader
实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader
实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。虚拟机中被称为 “bootstrap class loader” 的内置类加载器本身没有父类加载器,但是可以作为ClassLoader
实例的父类加载器。
从上面的介绍可以看出
ClassLoader
类使用委托模型来搜索类和资源- 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
ClassLoader
实例回在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
下图展示的各种类加载器之间的层次关系被称为类加载器的 "双亲委派模型(Parents Delegation Model)”
注意 ⚠️ :双亲委派模型并不是一种强制性的约束,只是 JDK 官方推荐的一种方式。如果我们因为某些特殊需求想要打破双亲委派模型也是可以的
另外,类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。
在面向对象编程中,有一条非常经典的设计原则:组合优于继承,多用组合少用继承。
双亲委派模型的执行流程
双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 java.lang.ClassLoader
的 loaderClass()
中
每当一个类加载器收到加载请求的时候,它会将请求转发给父类加载器。在父类加载器没有找到所请求类的情况下,该类加载器才会尝试去加载。
- 在类加载的时候,系统会首先判断当前类是否被加载过,已经被加载的类会直接返回,否则才会尝试加载(每个父类加载器都会重新走一遍这个流程)
- 类加载器进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父类加载器
loadClass()
方法来加载类)。这样的话,所有的请求最终都会传递到顶层的启动类加载器中进行加载 - 只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去架子啊(调用自己的
findClass()
方法来加载类) - 如果子类加载器也无法加载这个类,那么它会抛出一个
ClassNotFoundException
异常。
🌈扩展一下:
JVM 判断两个 Java 类是否相同的具体规则 : JVM 不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者相同的情况,才认为两个类是相同的。即使两个类来源于同一个 Class
文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相同。
双亲委派模型的好处
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
如果没有使用双亲委派模型,而是每个类加载器加载自己的话的就会出现一些问题,比如我们编写一个称为 java.lang.Object
类的话,那么程序运行时,系统就会出现两个不同的 Object
类。双亲委派模型可以保证加载的是 JRE 中的核心 API ,而不是我们自己写的 Object 类。这是因为 AppClassLoader
加载我们自己的类时候,根据双亲委派模型,会委派给启动类加载器进行加载,而启动类加载器发现自己已经加载了 Object
类,就会直接返回,不会加载我们自己写的 Object
类。
打破双亲委派模型方法
自定义加载器的话,需要继承 ClassLoader
。如果我们不想打破双亲委派模型,就重写 ClassLoader
类中的 findClass()
方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass()
方法。
为什么是重写 loadClass()
方法打破双亲委派模型呢?双亲委派模型的执行流程已经解释了:
类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器
loadClass()
方法来加载类)。
去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器 loadClass()
方法来加载类)。
重写 loadClass()
方法之后,我们就可以改变传统双亲委派模型的执行流程。例如,子类加载器可以在委派给父类加载器之前,先自己尝试加载这个类,或者在父类加载器返回之后,再尝试从其他地方加载这个类。具体的规则由我们自己实现,根据项目需求定制化。