浅谈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 双亲委托机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JVM双亲委派模型知识详细总结

    一.简介 除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的上层加载器,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给上层的加载器,如果上层类加载器还存在其上层类加载器,则进一步向上委托,依次递归,直到请求最终到达顶层的启动类加载器,从顶层类加载器开始,如果类加载器根据类的全限定名查询到已经加载过这个类,就成功返回加载过的此类信息,倘若加载器未加载过此类,则原路返回给下层加载器继续重复此过程,直到最先加载此类的加载器

  • java 详解类加载器的双亲委派及打破双亲委派

    java 详解类加载器的双亲委派及打破双亲委派 一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能.这时就需要自己定义类加载器,每个类加载器加载各自的类库资源,以此达到资源隔离效果.在对资源的加载上可以沿用双亲委派机制,也可以打破双亲委派机制. 一.沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可.如下例子: ①先定义一个待加载的类Test,它

  • JVM的类加载过程以及双亲委派模型详解

    jvm 的主要组成部分 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库接口(Native Interface) jvm 运行时数据区的组成 方法区: ①方法区主要用来存储已被虚拟机加载的类信息(构造器,接口定义).常量.静态变量和运行时常量池等数据. ②该区域是被线程共享的. ③方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用.该常量池具有动态性,也就是说常量并不一定是编译时确定,运行

  • JVM要双亲委派的原因及如何打破它

    一.类加载器 类加载器,顾名思义就是一个可以将Java字节码加载为java.lang.Class实例的工具.这个过程包括,读取字节数组.验证.解析.初始化等.另外,它也可以加载资源,包括图像文件和配置文件. 类加载器的特点: 动态加载,无需在程序一开始运行的时候加载,而是在程序运行的过程中,动态按需加载,字节码的来源也很多,压缩包jar.war中,网络中,本地文件等.类加载器动态加载的特点为热部署,热加载做了有力支持. 全盘负责,当一个类加载器加载一个类时,这个类所依赖的.引用的其他所有类都由这

  • 浅谈Tomcat如何打破双亲委托机制

    目录 JVM的类加载器 Tomcat的类加载器 findClass loadClass 我们经常会遇到ClassNotFound异常,表明JVM在尝试加载某类时失败了. 要解决这个异常,你得知道 什么是类加载 JVM如何加载类 为什么会出现ClassNotFound 想想Tomcat又是如何加载和管理Web应用下的Servlet呢? Tomcat正是通过Context组件来加载管理Web应用的,所以今天我会详细分析Tomcat的类加载机制.但在这之前,我们有必要预习一下JVM的类加载机制,我会先

  • Tomcat打破双亲委派机制实现隔离Web应用的方法

    目录 Tomcat类加载器的层次结构 WebAppClassLoader SharedClassLoader CatalinaClassLoader CommonClassLoader Spring的加载问题 线程上下文加载器 总结 Tomcat通过自定义类加载器WebAppClassLoader打破双亲委派,即重写了JVM的类加载器ClassLoader的findClass方法和loadClass方法,以优先加载Web应用目录下的类. Tomcat负责加载我们的Servlet类.加载Servl

  • 浅谈事件冒泡、事件委托、jQuery元素节点操作、滚轮事件与函数节流

    一.事件冒泡定义 事件冒泡是指在一个对象触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,甚至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层级的最顶层,即document对象(有些浏览器是window).. 二.事件冒泡的作用 事件冒泡允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元

  • 浅谈Tomcat内存配置的正确姿势

    1.背景 虽然阅读了各大牛的博客或文章,但并没有找到特别全面的关于JVM内存分配方法的文章,很多都是复制黏贴 为了严谨,本文特别备注只介绍基于HotSpot VM虚拟机,并且基于JDK1.7的内存分配情况,有关GC的说法也是基于CMS的concurrent collection(而非G1),防止大牛拍砖. 目前主流的JVM就是HotSpot VM(其次还有J9 VM,Zing VM),目前各类博客文章也大多基于JDK1.7以前的版本进行阐述的. (注:因为不同的虚拟机实现,不同的JDK,内存的分

  • 浅谈Tomcat多层容器的设计

    目录 容器的层次结构 请求定位Servlet的过程 工作原理 Tomcat的容器用来装载Servlet.那Tomcat的Servlet容器是如何设计的呢? 容器的层次结构 Tomcat设计了4种容器:Engine.Host.Context和Wrapper Tomcat通过这种分层,使得Servlet容器具有很好的灵活性. Context表示一个Web应用程序 Wrapper表示一个Servlet,一个Web应用程序中可能会有多个Servlet Host代表一个虚拟主机,或一个站点,可以给Tomc

  • 浅谈Tomcat三种运行模式

    tomcat的运行模式有3种 一.bio(blocking I/O) 即阻塞式I/O操作,表示Tomcat使用的是传统的Java I/O操作(即java.io包及其子包).是基于JAVA的HTTP/1.1连接器,Tomcat7以下版本在默认情况下是以bio模式运行的.一般而言,bio模式是三种运行模式中性能最低的一种.我们可以通过Tomcat Manager来查看服务器的当前状态.(Tomcat7 或以下,在 Linux 系统中默认使用这种方式) 二.nio(new I/O) 是Java SE

  • 浅谈Scrapy框架普通反爬虫机制的应对策略

    简单低级的爬虫速度快,伪装度低,如果没有反爬机制,它们可以很快的抓取大量数据,甚至因为请求过多,造成服务器不能正常工作.而伪装度高的爬虫爬取速度慢,对服务器造成的负担也相对较小. 爬虫与反爬虫,这相爱相杀的一对,简直可以写出一部壮观的斗争史.而在大数据时代,数据就是金钱,很多企业都为自己的网站运用了反爬虫机制,防止网页上的数据被爬虫爬走.然而,如果反爬机制过于严格,可能会误伤到真正的用户请求;如果既要和爬虫死磕,又要保证很低的误伤率,那么又会加大研发的成本. 简单低级的爬虫速度快,伪装度低,如果

  • 浅谈Qt QGraphics体系及刷新机制介绍

    概述 Qt的三大体系:QWidget.QGraphics.Quick,其中QGraphics图形框架算是这三个中比较高级的一种用法了,并且使用起来相比另外两个体系会更加的复杂一些,不过它能实现的功能却非常强大,主要体现在对图元的管理,它独特的刷新机制可以在众多的图元中都能够很好的管理,保证整个交互的流畅度. 而这里要描述的就是QGraphics体系的刷新机制以及该体系中相关元素的使用方式及特点. QGraphics体系的三大元素 QGraphics体系中最重要的三大元素:QGraphicsVie

  • 浅谈SpringBoot2.4 配置文件加载机制大变化

    前言 Spring Boot 2.4.0.M2刚刚发布,它对 application.properties 和 application.yml 文件的加载方式进行重构.如果应用程序仅使用单个 application.properties 或 application.yml 作为配置文件,那么可能感受不到任何区别.但是如果您的应用程序使用更复杂的配置(例如,Spring Cloud 配置中心等),则需要来了解更改的内容以及原因. 为什么要进行这些更改 随着最新版本 Spring Boot 发布,S

  • 浅谈Servlet的Cookie和Session机制

    一.Servlet Cookies Cookies定义:Cookies是存储在客户端计算机上的文本文件,并保留了用户的各种跟踪信息. Cookies作用:会话保持,如完成用户的登录与状态保持 Cookies的工作原理: 客户端向服务区发起登录请求 服务器脚本(代码)向浏览器发送一组Cookies,例如:姓名,年龄等 浏览器将这些信息存储在本地计算机上,以备将来使用 当下一次浏览器向web服务器发送任何请求时.浏览器会把这些Cookies信息发送到服务器,服务器将使用这些信息来识别账户 1.1 C

随机推荐