我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Tomcat是如何打破双亲委托机制的

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Tomcat是如何打破双亲委托机制的

本篇内容介绍了“Tomcat是如何打破双亲委托机制的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

目录
  • JVM的类加载器

  • Tomcat的类加载器

    • findClass

    • loadClass

我们经常会遇到ClassNotFound异常,表明JVM在尝试加载某类时失败了。

要解决这个异常,你得知道

  • 什么是类加载

  • JVM如何加载类

  • 为什么会出现ClassNotFound

想想Tomcat又是如何加载和管理Web应用下的Servlet呢?
Tomcat正是通过Context组件来加载管理Web应用的,所以今天我会详细分析Tomcat的类加载机制。但在这之前,我们有必要预习一下JVM的类加载机制,我会先回答一下一开始抛出来的问题,接着再谈谈Tomcat的类加载器如何打破Java的双亲委托机制。

JVM的类加载器

Java的类加载,就是把字节码格式.class文件加载到JVM的方法区,并在JVM堆建立一个java.lang.Class对象实例,封装Java类相关的数据和方法。

Class对象是什么?
可以理解成业务类的模板,JVM根据该模板创建具体业务类对象实例。

JVM并非在启动时就把所有 .class 文件都加载一遍,而是程序在运行过程中用到该类才去加载。
JVM类加载由类加载器完成,JDK提供一个抽象类ClassLoader:

public abstract class ClassLoader {    // 每个类加载器都有个父加载器    private final ClassLoader parent;        public Class<?> loadClass(String name) {          // 查找该类是否被加载过        Class<?> c = findLoadedClass(name);                // 若未被加载过        if( c == null ){          // 【递归】委托给父加载器加载          if (parent != null) {              c = parent.loadClass(name);          } else {              // 若父加载器为空,查找Bootstrap加载器是否加载过了              c = findBootstrapClassOrNull(name);          }        }        // 若父加载器未加载成功,调用自己的findClass去加载        if (c == null) {            c = findClass(name);        }                return c;    }        protected Class<?> findClass(String name){       // 1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存          ...                 // 2. 调用defineClass将字节数组转成Class对象       return defineClass(buf, off, len);    }        // 将字节码数组解析成一个Class对象,用native方法实现    protected final Class<?> defineClass(byte[] b, int off, int len){       ...    }}

JVM的类加载器是分层的父子关系,每个类加载器都持有一个parent字段指向父加载器。

  • defineClass 工具方法:调用native方法把Java类的字节码解析成一个Class对象

  • findClass 就是找到 .class 文件,可能来自文件系统或网络,找到后把 .class 文件读到内存得到字节码数组,然后调用defineClass方法得到Class对象

loadClass 首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。
这是个递归调用,即子加载器持有父加载器引用,当一个类加载器需加载一个Java类时,会先委托父加载器去加载,然后父加载器在自己加载路径中搜索Java类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制。

JDK的类加载器工作原理是一样的,区别只是加载路径不同,即findClass查找的路径不同。
双亲委托机制是为保证一个Java类在JVM的唯一性。假如你手滑写个与JRE核心类同名类,比如Object,双亲委托机制能保证加载的是JRE里的那个Object类,而不是你写的Object。
因为AppClassLoader在加载你的Object类时,会委托给ExtClassLoader去加载,而ExtClassLoader又会委托给BootstrapClassLoader,BootstrapClassLoader发现自己已经加载过了Object类,会直接返回,不会去加载你的Object类。

类加载器的父子关系不是通过继承来实现的,比如AppClassLoader并非ExtClassLoader的子类,只是AppClassLoader的parent指向ExtClassLoader对象。
所以若自定义类加载器,不是去继承AppClassLoader,而是继承ClassLoader抽象类,再重写findClass和loadClass即可。
Tomcat就是通过自定义类加载器实现自己的类加载。
若你要打破双亲委托,也就只需重写loadClass,因为loadClass的默认实现就是双亲委托机制。

Tomcat的类加载器

Tomcat的自定义类加载器WebAppClassLoader打破了双亲委托机制:
首先自己尝试去加载某个类,如果找不到再委托给父类加载器,目的是优先加载Web应用自己定义的类。
只需重写ClassLoader的两个方法:

findClass

public Class<?> findClass(String name) throws ClassNotFoundException {    ...        Class<?> clazz = null;    try {            //1. 先在Web应用目录下查找类             clazz = findClassInternal(name);    }  catch (RuntimeException e) {           throw e;       }        if (clazz == null) {    try {            //2. 如果在本地目录没有找到,交给父加载器去查找            clazz = super.findClass(name);    }  catch (RuntimeException e) {           throw e;       }        //3. 如果父类也没找到,抛出ClassNotFoundException    if (clazz == null) {        throw new ClassNotFoundException(name);     }    return clazz;}

工作流程

  • 先在Web应用本地目录下查找要加载的类

  • 若未找到,交给父加载器查找,即AppClassLoader

  • 若父加载器也没找到这个类,抛ClassNotFound

loadClass

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {    synchronized (getClassLoadingLock(name)) {         Class<?> clazz = null;        //1. 先在本地cache查找该类是否已经加载过        clazz = findLoadedClass0(name);        if (clazz != null) {            if (resolve)                resolveClass(clazz);            return clazz;        }        //2. 从系统类加载器的cache中查找是否加载过        clazz = findLoadedClass(name);        if (clazz != null) {            if (resolve)                resolveClass(clazz);            return clazz;        }        // 3. 尝试用ExtClassLoader类加载器类加载,为什么?        ClassLoader javaseLoader = getJavaseClassLoader();        try {            clazz = javaseLoader.loadClass(name);            if (clazz != null) {                if (resolve)                    resolveClass(clazz);                return clazz;            }        } catch (ClassNotFoundException e) {            // Ignore        }        // 4. 尝试在本地目录搜索class并加载        try {            clazz = findClass(name);            if (clazz != null) {                if (resolve)                    resolveClass(clazz);                return clazz;            }        } catch (ClassNotFoundException e) {            // Ignore        }        // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载            try {                clazz = Class.forName(name, false, parent);                if (clazz != null) {                    if (resolve)                        resolveClass(clazz);                    return clazz;                }            } catch (ClassNotFoundException e) {                // Ignore            }       }        //6. 上述过程都加载失败,抛出异常    throw new ClassNotFoundException(name);}

工作流程

  • 先在本地Cache查找该类是否已加载过

  • 即Tomcat的类加载器是否已经加载过这个类。

  • 若Tomcat类加载器尚未加载过该类,再看看系统类加载器是否加载过

  • 若都没有,就让ExtClassLoader加载,为防止Web应用自己的类覆盖JRE的核心类

  • 因为Tomcat需打破双亲委托,假如Web应用里自定义了一个叫Object的类,若先加载该Object类,就会覆盖JRE的Object类,所以Tomcat类加载器优先尝试用ExtClassLoader去加载,因为ExtClassLoader会委托给BootstrapClassLoader去加载,BootstrapClassLoader发现自己已经加载了Object类,直接返回给Tomcat的类加载器,这样Tomcat的类加载器就不会去加载Web应用下的Object类了,避免覆盖JRE核心类。

  • 若ExtClassLoader加载失败,即JRE无此类,则在本地Web应用目录下查找并加载

  • 若本地目录下无此类,说明不是Web应用自己定义的类,那么由系统类加载器去加载。这里请你注意,Web应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。

  • 若上述加载过程都失败,抛ClassNotFound

可见 Tomcat 类加载器打破了双亲委托,没有一上来就直接委托给父加载器,而是先在本地目录下加载。
但为避免本地目录类覆盖JRE核心类,会先尝试用ExtClassLoader加载。
那为何不先用AppClassLoader加载?
若这样,就又变成双亲委托,这就是Tomcat类加载器的奥妙。

“Tomcat是如何打破双亲委托机制的”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Tomcat是如何打破双亲委托机制的

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Tomcat是如何打破双亲委托机制的

本篇内容介绍了“Tomcat是如何打破双亲委托机制的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!目录JVM的类加载器Tomcat的类加载器
2023-06-20

C++ 函数的友元机制如何打破封装性?

c++++ 中,友元机制打破封装,允许函数或类访问其他类的私有成员。通过使用 friend 关键字,可将函数声明为某个类的友元,从而访问其私有数据和方法。友元机制常用于解决类之间的循环依赖,例如允许构造函数互相成为友元,访问对方私有成员初始
C++ 函数的友元机制如何打破封装性?
2024-04-11

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录