Tomcat为什么要重写类加载器?

本文详细解释了Tomcat如何利用双亲委派机制实现类的隔离,以及为何打破机制以支持多版本应用和热部署。重点介绍了各类Tomcat类加载器的工作原理和作用,强调了隔离性和热部署的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、双亲委派机制

首先了解下双亲委派机制,大致过程如下:
在这里插入图片描述
简单来说,就是加载class文件时,委托上级加载器去加载,依次传递到顶层类加载器,加载过,直接返回;没加载过,尝试让下级进行加载,依次传递回去;

保证每个类只会被加载一次,避免重复加载。

二、分析

1、Tomcat需要隔离性

  • 如果使用默认的类加载器,是无法加载两个相同类库不同版本的
  • web容器部署多个应用程序,不同应用可能依赖相同类库不同版本,需要保证每个应用程序类独立、相互隔离
  • 部署在同个web容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进JVM
  • web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离

2、Tomcat需要热替换

jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的

3、打破双亲委派机制

  • OSGI是基于Java语言的动态模块化规范,类加载器之间是网状结构,更加灵活,但是也更复杂
  • JNDI服务,使用线程上线文类加载器,父类加载器去使用子类加载器

三、Tomcat类加载器

1、拓展类加载器

在这里插入图片描述
说明如下:

  • CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和各个webapp访问
  • CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的class,即对webapp不可见
  • SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见
  • WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
    每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个JspClassLoader,所以这两个类加载器有多个实例

2、工作原理

  • a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用
  • b. CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离
  • c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系
  • d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能

tomcat目录结构,与上面的类加载器对应

  • /common/*
  • /server/*
  • /shared/*
  • /WEB-INF/*

默认情况下,conf目录下的catalina.properties文件,没有指定server.loader以及shared.loader,所以tomcat没有建立CatalinaClassLoader和SharedClassLoader的实例,这两个都会使用CommonClassLoader来代替。Tomcat6之后,把common、shared、server目录合成了一个lib目录。所以在我们的服务器里看不到common、shared、server目录

四、总结

Tomcat重写类加载器的主要目的是为了实现Web应用程序的隔离和热部署。

  • 隔离性:Tomcat使用自定义的类加载器来加载每个Web应用程序的类。这样可以确保每个Web应用程序在运行时具有自己的类加载器实例,从而实现了应用程序之间的隔离。这样一来,不同的Web应用程序可以使用相同的类名,但它们的类加载器会将它们加载到各自的命名空间中,避免了类冲突的问题。

  • 热部署:Tomcat的类加载器还支持热部署功能,即在应用程序运行时动态加载和卸载类。当Web应用程序的代码或资源发生变化时,Tomcat可以重新加载这些变化的部分,而无需重启整个服务器。这样可以提高开发和调试的效率。

通过重写类加载器,Tomcat能够实现更灵活的类加载和隔离机制,使得Web应用程序的部署和维护更加方便和高效。

<think>是的,在Java中会同时存在多个类加载器,它们共同构成**分层树状结构**(双亲委派模型)。以下是典型场景中的类加载器共存示例: ### 一、JVM默认的三层类加载器 1. **Bootstrap ClassLoader**(启动类加载器) - 由C++实现,加载`JRE/lib`下的核心库(如`rt.jar`) - **共存证明**:`String.class.getClassLoader()`返回`null`(代表由它加载) 2. **Extension ClassLoader**(扩展类加载器) - Java实现,加载`JRE/lib/ext`目录的扩展包 - **共存证明**: ```java ClassLoader extLoader = sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader(); System.out.println(extLoader); // 输出sun.misc.Launcher$ExtClassLoader@xxx ``` 3. **Application ClassLoader**(应用类加载器) - 加载`classpath`下的用户类 - **共存证明**: ```java System.out.println(ClassLoader.getSystemClassLoader()); // 输出sun.misc.Launcher$AppClassLoader@xxx ``` ### 二、自定义类加载器共存场景 **案例:Web容器(如Tomcat)** ```java // 每个Web应用有独立的WebappClassLoader ClassLoader webAppLoader1 = new WebappClassLoader(/*app1*/); ClassLoader webAppLoader2 = new WebappClassLoader(/*app2*/); // 不同应用加载相同类名但不同版本的类 Class<?> clazz1 = webAppLoader1.loadClass("com.example.Service"); Class<?> clazz2 = webAppLoader2.loadClass("com.example.Service"); System.out.println(clazz1 == clazz2); // 输出false(类隔离) ``` ### 三、特殊共存场景 1. **线程上下文类加载器(TCCL)** ```java Thread.currentThread().setContextClassLoader(customLoader); // 框架中可通过Thread.currentThread().getContextClassLoader()获取 ``` 2. **SPI机制** JDBC驱动加载时,通过`ServiceLoader`使用TCCL加载实现类: ```java Connection conn = DriverManager.getConnection(url); // 内部使用TCCL加载驱动 ``` ### 四、类加载器共存的关键规则 - **双亲委派**:子加载器优先委派父加载器加载(防止核心类被篡改) - **并行加载**:不同类加载器可同时加载不同类(提升性能) - **命名空间隔离**:不同类加载器加载的类被视为不同类(即使全限定名相同) §§相关问题§§ 1. 双亲委派模型如何避免核心类被自定义类覆盖?请描述其委派流程。 2. Tomcat的WebappClassLoader为什么需要打破双亲委派机制? 3. 如何利用线程上下文类加载器(TCCL)解决SPI中的类加载困境? 4. 在OSGi框架中,类加载器的网状结构如何实现模块化隔离? 5. 自定义类加载器重写`findClass()`方法时,为什么要避免覆盖`loadClass()`?</think>是的,在Java中会**同时存在多个类加载器**,它们共同构成层级化的类加载体系。以下是典型场景的说明和示例: --- ### 一、JVM默认的三层类加载器共存 1. **启动类加载器(Bootstrap ClassLoader)** - C++实现,加载`JRE/lib`核心库(如`rt.jar`) - 示例:`String.class.getClassLoader()`返回`null`(表示由它加载) 2. **扩展类加载器(Extension ClassLoader)** - Java实现,加载`JRE/lib/ext`扩展包 - 示例: ```java ClassLoader extLoader = sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader(); System.out.println(extLoader); // 输出 sun.misc.Launcher$ExtClassLoader@xxx ``` 3. **应用类加载器(Application ClassLoader)** - 加载`classpath`用户类 - 示例: ```java System.out.println(ClassLoader.getSystemClassLoader()); // 输出 sun.misc.Launcher$AppClassLoader@xxx ``` --- ### 二、自定义类加载器共存场景 **案例1:Web容器(如Tomcat)** ```java // 每个Web应用有独立的WebappClassLoader ClassLoader webAppLoader1 = new WebappClassLoader(/*app1*/); ClassLoader webAppLoader2 = new WebappClassLoader(/*app2*/); // 不同应用加载相同类名但不同版本的类 Class<?> clazz1 = webAppLoader1.loadClass("com.example.Service"); Class<?> clazz2 = webAppLoader2.loadClass("com.example.Service"); System.out.println(clazz1 == clazz2); // 输出 false(类隔离) ``` **案例2:OSGi模块化框架** ```java // BundleA和BundleB使用不同的类加载器 ClassLoader bundleALoader = framework.getBundle("A").getClassLoader(); ClassLoader bundleBLoader = framework.getBundle("B").getClassLoader(); // 即使加载相同接口,也会生成不同的Class对象 Class<?> serviceA = bundleALoader.loadClass("com.Service"); Class<?> serviceB = bundleBLoader.loadClass("com.Service"); System.out.println(serviceA.equals(serviceB)); // 输出 false ``` **案例3:热部署场景** ```java // 每次更新代码时创建新类加载器 URLClassLoader v1Loader = new URLClassLoader(new URL[]{jarV1}); URLClassLoader v2Loader = new URLClassLoader(new URL[]{jarV2}); // v1和v2的类同时存在 Object serviceV1 = v1Loader.loadClass("Service").newInstance(); Object serviceV2 = v2Loader.loadClass("Service").newInstance(); ``` --- ### 三、特殊共存场景 1. **线程上下文类加载器(TCCL)** ```java Thread.currentThread().setContextClassLoader(customLoader); // 框架中可通过Thread.currentThread().getContextClassLoader()获取 ``` 2. **SPI机制** JDBC驱动加载时,通过`ServiceLoader`使用TCCL加载实现类: ```java Connection conn = DriverManager.getConnection(url); // 内部使用TCCL加载驱动 ``` --- ### 四、类加载器共存的关键规则 - **双亲委派**:子加载器优先委派父加载器加载(防止核心类被篡改) - **并行加载**:不同类加载器可同时加载不同类(提升性能) - **命名空间隔离**:不同类加载器加载的类被视为不同类(即使全限定名相同)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Forlan

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值