浅谈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如何打破双亲委托机制的文章就介绍到这了,更多相关Tomcat 双亲委托机制内容请搜索真格学网以前的文章或继续浏览下面的相关文章希望大家以后多多支持真格学网!

您可能感兴趣的文章:Tomcat进程占用CPU过高的解决方法SpringBoot启动嵌入式Tomcat的实现步骤Tomcat打破双亲委派机制实现隔离Web应用的方法使用tomcat设定shared lib共享同样的jar十五道tomcat面试题,为数不多的机会!

  • 本文相关:
  • eclipse启动tomcat后无法访问项目解决办法
  • vue实现表单数据验证的实例代码
  • 非常实用的tomcat启动脚本实现方法
  • 使用tomcat设定shared lib共享同样的jar
  • tomcat部署多个war包的方法步骤
  • tomcat+mysql高并发配置优化讲解
  • apache结合tomcat实现动静分离的方法
  • 浅谈tomcat乱码与端口占用的解决方案
  • 解决tomcat在debug模式下无法启动问题
  • 在tomcat中部署web项目的操作方法(必看篇)
  • 如何在服务中卸载tomcat
  • 如何添加Tomcat为启动服务
  • 如何查看TOMCAT 是多少位的
  • 如何把java程序部署到tomcat里
  • 如何强制关闭Tomcat,同时杀死进程
  • 安装版tomcat怎么配置jdk的路径
  • tomcat怎么配置地址映射
  • tomcat怎么定义 内存大小呢?
  • tomcat如何打补丁?
  • docker tomcat 怎么上传文件
  • tomcat 怎么配置Servlet
  • tomcat怎么运行html文件?
  • linux中怎么查看tomcat是否已启动
  • e-tomcat如何安装?
  • tomcat部署项目如何去掉项目名称?急急急,在线等
  • 请问tomcat如何配置数据库连接池,使得连接中断后自动重连?
  • myeclipse中如何使用tomcat8.0
  • 怎样查看电脑是否安装了tomcat?
  • eclipse中tomcat无法启动,如何解决
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全星外虚拟主机华众虚拟主机linuxwin服务器ftp服务器dns服务器tomcat nginxzabbix云和虚拟化服务器其它首页服务器tomcat tomcat进程占用cpu过高的解决方法springboot启动嵌入式tomcat的实现步骤tomcat打破双亲委派机制实现隔离web应用的方法使用tomcat设定shared lib共享同样的jar十五道tomcat面试题,为数不多的机会!eclipse启动tomcat后无法访问项目解决办法vue实现表单数据验证的实例代码非常实用的tomcat启动脚本实现方法使用tomcat设定shared lib共享同样的jartomcat部署多个war包的方法步骤tomcat+mysql高并发配置优化讲解apache结合tomcat实现动静分离的方法浅谈tomcat乱码与端口占用的解决方案解决tomcat在debug模式下无法启动问题在tomcat中部署web项目的操作方法(必看篇)tomcat7.0安装配置详细(图文)直接双击启动tomcat中的startup.tomcat中更改网站根目录和默认页在tomcat中部署web项目的操作方法如何修改tomcat默认端口号8080的关于tomcat的server.xml里host节tomcat环境变量详细配置步骤tomcat启动报错:java.util.zip.tomcat启动startup.bat一闪而过问启动tomcat时 错误: 代理抛出异常idea中tomcat启动源码调试进入到tomcat内apache及tomcat搭建集群环境过程解析idea 配置tomcat服务器和发布web项目的图tomcat配置https的方法示例tomcat中redirectport的作用tomcat 中如何给 web 项目配置虚拟目录的tomcat何时写回响应数据报的详析tomcat并发优化方法介绍tomcat报错: jdbc unregister 解决办法tomcat常见异常及解决方案代码实例
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved