• 售前

  • 售后

热门帖子
入门百科

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

[复制链接]
123456809 显示全部楼层 发表于 2021-8-14 14:39:45 |阅读模式 打印 上一主题 下一主题
目录


  • 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:
  1. public abstract class ClassLoader {
  2.     // 每个类加载器都有个父加载器
  3.     private final ClassLoader parent;
  4.    
  5.     public Class<?> loadClass(String name) {
  6.   
  7.         // 查找该类是否被加载过
  8.         Class<?> c = findLoadedClass(name);
  9.         
  10.         // 若未被加载过
  11.         if( c == null ){
  12.           // 【递归】委托给父加载器加载
  13.           if (parent != null) {
  14.               c = parent.loadClass(name);
  15.           } else {
  16.               // 若父加载器为空,查找Bootstrap加载器是否加载过了
  17.               c = findBootstrapClassOrNull(name);
  18.           }
  19.         }
  20.         // 若父加载器未加载成功,调用自己的findClass去加载
  21.         if (c == null) {
  22.             c = findClass(name);
  23.         }
  24.         
  25.         return c;
  26.     }
  27.    
  28.     protected Class<?> findClass(String name){
  29.        // 1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存
  30.           ...
  31.          
  32.        // 2. 调用defineClass将字节数组转成Class对象
  33.        return defineClass(buf, off, len);
  34.     }
  35.    
  36.     // 将字节码数组解析成一个Class对象,用native方法实现
  37.     protected final Class<?> defineClass(byte[] b, int off, int len){
  38.        ...
  39.     }
  40. }
复制代码
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

  1. public Class<?> findClass(String name) throws ClassNotFoundException {
  2.     ...
  3.    
  4.     Class<?> clazz = null;
  5.     try {
  6.             //1. 先在Web应用目录下查找类
  7.             clazz = findClassInternal(name);
  8.     }  catch (RuntimeException e) {
  9.            throw e;
  10.        }
  11.    
  12.     if (clazz == null) {
  13.     try {
  14.             //2. 如果在本地目录没有找到,交给父加载器去查找
  15.             clazz = super.findClass(name);
  16.     }  catch (RuntimeException e) {
  17.            throw e;
  18.        }
  19.    
  20.     //3. 如果父类也没找到,抛出ClassNotFoundException
  21.     if (clazz == null) {
  22.         throw new ClassNotFoundException(name);
  23.      }
  24.     return clazz;
  25. }
复制代码
工作流程
       
  • 先在Web应用本地目录下查找要加载的类   
  • 若未找到,交给父加载器查找,即AppClassLoader   
  • 若父加载器也没找到这个类,抛ClassNotFound

loadClass

  1. public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  2.     synchronized (getClassLoadingLock(name)) {
  3.         Class<?> clazz = null;
  4.         //1. 先在本地cache查找该类是否已经加载过
  5.         clazz = findLoadedClass0(name);
  6.         if (clazz != null) {
  7.             if (resolve)
  8.                 resolveClass(clazz);
  9.             return clazz;
  10.         }
  11.         //2. 从系统类加载器的cache中查找是否加载过
  12.         clazz = findLoadedClass(name);
  13.         if (clazz != null) {
  14.             if (resolve)
  15.                 resolveClass(clazz);
  16.             return clazz;
  17.         }
  18.         // 3. 尝试用ExtClassLoader类加载器类加载,为什么?
  19.         ClassLoader javaseLoader = getJavaseClassLoader();
  20.         try {
  21.             clazz = javaseLoader.loadClass(name);
  22.             if (clazz != null) {
  23.                 if (resolve)
  24.                     resolveClass(clazz);
  25.                 return clazz;
  26.             }
  27.         } catch (ClassNotFoundException e) {
  28.             // Ignore
  29.         }
  30.         // 4. 尝试在本地目录搜索class并加载
  31.         try {
  32.             clazz = findClass(name);
  33.             if (clazz != null) {
  34.                 if (resolve)
  35.                     resolveClass(clazz);
  36.                 return clazz;
  37.             }
  38.         } catch (ClassNotFoundException e) {
  39.             // Ignore
  40.         }
  41.         // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
  42.             try {
  43.                 clazz = Class.forName(name, false, parent);
  44.                 if (clazz != null) {
  45.                     if (resolve)
  46.                         resolveClass(clazz);
  47.                     return clazz;
  48.                 }
  49.             } catch (ClassNotFoundException e) {
  50.                 // Ignore
  51.             }
  52.        }
  53.    
  54.     //6. 上述过程都加载失败,抛出异常
  55.     throw new ClassNotFoundException(name);
  56. }
复制代码
工作流程

       
  • 先在本地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 双亲委托机制内容请搜索草根技术分享以前的文章或继承浏览下面的相关文章盼望各人以后多多支持草根技术分享!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作