tomcat的类加载器结构

在Tomcat目录结构中,有三组目录(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java类库,此外还有第四组Web应用程序自身的目录“/WEB-INF/*”,把java类库放置在这些目录中的含义分别是:

  • 放置在common目录中:类库可被Tomcat和所有的Web应用程序共同使用。

  • 放置在server目录中:类库可被Tomcat使用,但对所有的Web应用程序都不可见。

  • 放置在shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。

  • 放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

  • 对于tomcat开发者来说,哪些类需要被公用,哪些类需要被隔离是很清楚的,对于类放在哪个位置被那个类加载器加载是很清楚的,我们一般关注的只是webapp中自定义的类。

为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,如下图所示

灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的作用前面已经介绍过了。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/*、/server/*、/shared/* 和 /WebApp/WEB-INF/* 中的 Java 类库。其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。

从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。

tomcat的类加载(loadClass)过程

和原本的双亲委派模型思路差不多,先看有没有加载过。

  1. 先在本地 Cache 查找该类是否已经加载过,也就是说 Tomcat 的类加载器是否已经加载过这个类。

  1. 如果 Tomcat 类加载器没有加载过这个类,再看看系统类加载器是否加载过。

  1. 如果都没有,就让ExtClassLoader去加载,这一步比较关键,目的防止 Web 应用自己的类覆盖 JRE 的核心类。因为 Tomcat 需要打破双亲委托机制,假如 Web 应用里自定义了一个叫 Object 的类,如果先加载这个 Object 类,就会覆盖 JRE 里面的那个 Object 类,这就是为什么 Tomcat 的类加载器会优先尝试用 ExtClassLoader 去加载,因为 ExtClassLoader 会委托给 BootstrapClassLoader 去加载,BootstrapClassLoader 发现自己已经加载了 Object 类,直接返回给 Tomcat 的类加载器,这样 Tomcat 的类加载器就不会去加载 Web 应用下的 Object 类了,也就避免了覆盖 JRE 核心类的问题。

  1. 如果 ExtClassLoader 加载器加载失败,也就是说 JRE 核心类中没有这类,那么就在本地 Web 应用目录下查找并加载。

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

  1. 如果上述加载过程全部失败,抛出 ClassNotFound 异常。

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);
}

tomcat的类加载(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. 如果父类也没找到,抛出 ClassNotFoundExceptionif (clazz == null) {throw new ClassNotFoundException(name);}return clazz;
}

findClass方法大致步骤:

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

  1. 如果没有找到,交给父加载器去查找,它的父加载器就是上面提到的系统类加载器 AppClassLoader。

  1. 如何父加载器也没找到这个类,抛出 ClassNotFound 异常。

Tomcat是如何打破双亲委派机制的呢?

Tomcat是先去本地目录加载,为了避免本地目录覆盖掉JRE的核心类,如java.lang包等,先尝试用ExtClassLoader加载,这样即能打破双亲委派机制,有避免了覆盖掉核心类。

为什么不是尝试用AppClassLoader加载呢?

如果是尝试用AppClassLoader,这样不又变会双亲委派机制了嘛。

ThreadContextClassLoader

SPI机制简介

SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

SPI具体约定

Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

Spring加载问题

Tomcat 加载器的实现清晰易懂,并且采用了官方推荐的“正统”的使用类加载器的方式。这时作者提一个问题:如果有 10 个 Web 应用程序都用到了spring的话,可以把Spring的jar包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个web应用程序的bean,getBean时自然要能访问到应用程序的类,而用户的程序显然是放在 /WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class呢?

解答

答案呼之欲出:spring根本不会去管自己被放在哪里,它统统使用TCCL来加载类,而TCCL默认设置为了WebAppClassLoader,也就是说哪个WebApp应用调用了spring,spring就去取该应用自己的WebAppClassLoader来加载bean,简直完美~

Tomcat打破双亲委派模型相关推荐

  1. Tomcat打破双亲委派机制

    打破双亲委派 沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载自己实现的 java.lang.String.class public class MyClassLoaderTest {st ...

  2. 如何打破双亲委派模型?打破双亲委派模型示例?什么是双亲委派模型?

    什么是双亲委派模型? 双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加 ...

  3. 双亲委派模型为什么要打破双亲委派模型

    目录 一:什么是双亲委派模型 二:什么情况下要打破双亲委派模型 三:如何打破 一:什么是双亲委派模型 Java 虚拟机对 class 文件采用的是按需加载的方式(也可以称之为懒加载),也就是说当 需要 ...

  4. 如何破坏双亲委派模型

    起源来自于网易面试官的一个问题,一个类的静态块是否可能被执行两次.众所周知类加载的初始化阶段会自动收集类中所有类变量的赋值动作与静态语句块中的语句生成一个()方法,这个方法只会被执行一次.因此通常的理 ...

  5. 【有料】面试必备:什么时候要打破双亲委派机制?什么是双亲委派? (图解+秒懂+史上最全)

    面试题:什么时候要打破双亲委派机制 来自社群的两个面试题,其实也是两个基础的 面试题,大家一定要掌握 社群问题: 先说下第一题的结论 场景1: 如果委托类没有实现接口的话,就不能使用newProxyI ...

  6. 15.什么是双亲委派模型?

    什么是双亲委派机制? 双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委 派给它的父加载器进行加载.这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加 载. 双亲 ...

  7. 面试必备:什么时候要打破双亲委派机制?什么是双亲委派? (图解+秒懂+史上最全)

    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 ...

  8. JVM-双亲委派机制以及打破双亲委派

    目录 类加载器和双亲委派机制 类加载器初始化过程 为什么要设计双亲委派机制 为什么要有双亲委派? 自定义类加载器 打破双亲委派机制 Tomcat打破双亲委派机制 Tomcat 如果使用默认的双亲委派类 ...

  9. 自定义类加载器以及打破双亲委派

    0x01 自定义类加载器 自定义类加载器加载一个类需要:继承ClassLoader,重写findClass,如果不想打破双亲委派模型,那么只需要重写findClass:如果想打破双亲委派模型,那么就重 ...

最新文章

  1. 强大的Charles的使用,强大的flutter1.9
  2. 缓存区溢出漏洞工具Doona
  3. 【实验】不会端口映射?看完就会了
  4. Java垃圾回收(GC)、找垃圾的方式、GC Root、GC停顿、引用、垃圾收集算法、收集器、GC日志、安全点、安全区域
  5. 浅析php-fpm静态和动态执行方式的比较
  6. 随机交换检验数据挖掘结果-assessing data mining result via swap randomization
  7. C#教程第四课:循环控制语句
  8. #语音信号处理基础(十一)——梅尔倒谱系数的提取
  9. 浅谈数据结构之顺序队列(五)
  10. Cinema 4D R25 for mac(c4d r25)快捷键分享
  11. P-SIF长文本表示方法
  12. mac安装telnet命令
  13. maxscale mysql5.7_Centos7安装maxscale 实现mysql的读写分离
  14. Unity资源管理(一)
  15. eq值 推荐算法_EQ,IQ,EIQ分析
  16. python的turtle的画太阳花(简洁和经典)
  17. 一文彻底搞懂微服务架构
  18. 新手学习电脑知识的一些方法 oldtimeblog
  19. 仿微信二维码极速扫描(MLKit及CameraX初体验),安卓消息分发机制
  20. 理财通app的设计与实现(六)

热门文章

  1. 图像算法工程师常考的面试问题附回答
  2. Linux内核文件系统12
  3. PHPRunner 10.7.0 PHP代码生成器
  4. 路由器、交换机、集线器工作在哪一层
  5. 法国大数据分析服务初创公司 Dataiku 获1400 万美元 A 轮融资
  6. 微信公众号接口开发----退款
  7. 计算机如何在本地硬盘安装WinPE系统
  8. 电脑系统还原怎么操作?
  9. “特斯拉杀手们”真正的敌人来了
  10. 山东交通学院linux期末考试题,中国近现代史纲要(山东联盟-山东交通学院版)2020知到章节期末答案...