Tomcat源码分析--类加载器

news/2024/5/17 16:14:23/文章来源:https://www.cnblogs.com/yishi-san/p/16609553.html

Tomcat类加载器结构

上图是Tomcat文档中所展示的Tomcat类加载结构。在这个结构中Bootstartap和System的类加载器由java虚拟机实现。common类加载器由Tomcat容器实现,它对 Tomcat 内部类和所有 Web 应用程序都是可见的。此类加载器搜索的位置$CATALINA_BASE/conf/catalina.properties 中的common.loader属性定义。在catalina.properties文件也定义了server.loader和shared.loader属性,这两个属性分别由Server和Shared两个类加载器加载。

接下来让我们看一下Tomcat如何实现common类加载器。首先我们需要找到Bootstrap类的main方法。在main方中有一段代码如下,这段代码的大意是先判断Bootstrap是否为null,不为null,直接将Catalina ClassLoader设置到当前线程,用于加载服务器相关类,为null则进入bootstrap的init方法。

......
synchronized (daemonLock) {if (daemon == null) {// Don't set daemon until init() has completedBootstrap bootstrap = new Bootstrap();try {bootstrap.init();} catch (Throwable t) {handleThrowable(t);t.printStackTrace();return;}daemon = bootstrap;} else {// When running as a service the call to stop will be on a new// thread so make sure the correct class loader is used to// prevent a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);}}
......

init方法会调用initClassLoaders()方法,在该方法中会调用createClassLoader方法创建commonLoader、catalinaLoader、sharedLoader三种类加载器。与上文中所介绍的三种类加载器一一对应。

 private void initClassLoaders() {try {commonLoader = createClassLoader("common", null);if (commonLoader == null) {// no config file, default to this loader - we might be in a 'single' env.commonLoader = this.getClass().getClassLoader();}catalinaLoader = createClassLoader("server", commonLoader);sharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}

createClassLoader方法终会调用如下代码,通过这段代码可以知到commonLoader是一个URLClassLoader。

public static ClassLoader createClassLoader(List<Repository> repositories,final ClassLoader parent)throws Exception {
......
return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {@Overridepublic URLClassLoader run() {if (parent == null)return new URLClassLoader(array);elsereturn new URLClassLoader(array, parent);}});
}

Common|Catalina|Shared的使用

Bootstartap中的init方法中调用完initClassLoaders方法后,就开始了对类加载器的使用。Tomcat用catalinaLoader来加载Catalina类,这个类就是我们经常说的容器。加载完Catalina后会将sharedLoader作为参数传递给Catalina类,以便于后续给Webappx设置父加载器。

public void init() throws Exception {initClassLoaders();......// 加载Catalina类Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.getConstructor().newInstance();// 设置sharedLoad加载器   String methodName = "setParentClassLoader";Class<?> paramTypes[] = new Class[1];paramTypes[0] = Class.forName("java.lang.ClassLoader");Object paramValues[] = new Object[1];paramValues[0] = sharedLoader;Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);catalinaDaemon = startupInstance;}

WebappX 加载器

上文介绍了common类加载器的创建和使用,那么Webappx类加载器又是如何被创建和使用的呢?

创建WebappX 加载器

在看Tomcat源码是如何创建webappx类加载器之前,让我们来做一个实验。假设我们已经编译好了一个全限定名为com.example.WebAppClassLoader.class文件,那么应该如何来加载这个类文件呢?一种方法是自定义一个类加载器。

public class MyClassLoader extends URLClassLoader {public MyClassLoader() {super(new URL[0]);}@Overrideprotected Class<?> findClass(String name) {String myPath = "file:///D:/www/tomact-test-war/tomcat-test-case008/src/main/webapp/WEB-INF/" + name.replace(".","/") + ".class";byte[] cLassBytes = null;Path path = null;try {path = Paths.get(new URI(myPath));cLassBytes = Files.readAllBytes(path);} catch (IOException | URISyntaxException e) {e.printStackTrace();}Class clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);return clazz;}
}

创建一个名为MyClassLoader的类并继承URLClassLoader类,重写findClass方法,一个简单的类加载器就创建完成了。

public static void main(String[] args) throws Exception {MyClassLoader cl = new MyClassLoader();Class<?> wacl = cl.findClass("com.example.WebAppClassLoader");try {Object obj = wacl.newInstance();Method method = wacl.getMethod("test_1");method.invoke(obj);} catch (Exception e) {e.printStackTrace();}}
package com.example;
public class WebAppClassLoader {public WebAppClassLoader() {}public void test_1() {System.out.println("自定类加载器");}
}

在main方法中创建MyClassLoader对象,并调用findClass方法,至此就将一个class文件加载到了虚拟机中。

好了,实验做完后,让我们回过头来看看Tomcat是如何创建WebappX类加载器的。

在StandardContext的startInternal方法中有这样一段代码

if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader();webappLoader.setDelegate(getDelegate());setLoader(webappLoader);}

它会创建WebappLoader对象,并通过setLoader(webappLoader)赋值到一个实例变量中,然后会调用WebappLoader的start方法:

......
classLoader = createClassLoader();classLoader.setResources(context.getResources());classLoader.setDelegate(this.delegate);
......

进入createClassLoader方法:

private WebappClassLoaderBase createClassLoader()throws Exception {Class<?> clazz = Class.forName(loaderClass);WebappClassLoaderBase classLoader = null;if (parentClassLoader == null) {parentClassLoader = context.getParentClassLoader();} else {context.setParentClassLoader(parentClassLoader);}Class<?>[] argTypes = { ClassLoader.class };Object[] args = { parentClassLoader };Constructor<?> constr = clazz.getConstructor(argTypes);classLoader = (WebappClassLoaderBase) constr.newInstance(args);return classLoader;}

该方法会实例化一个ParallelWebappClassLoader实例,并且传递了sharedLoader作为其父亲加载器。

ParallelWebappClassLoader继承了WebappClassLoaderBase抽象类,WebappClassLoaderBase继承了URLClassLoader。在WebappClassLoaderBase类中重写了findClass方法。至此WebappX类加载器 就创建完成了。

那Webappx类加载器又是被如何使用的呢?

还记得在Tomcat动态部署一章介绍的那个webConfig方法吗?这个方法非常复杂。在这个方法的第四步中会调用populateJavaClassCache方法

private void populateJavaClassCache(String className,Map<String,JavaClassCacheEntry> javaClassCache) {......try (InputStream is = context.getLoader().getClassLoader().getResourceAsStream(name)) {if (is == null) {return;}ClassParser parser = new ClassParser(is);JavaClass clazz = parser.parse();populateJavaClassCache(clazz.getClassName(), clazz, javaClassCache);} catch (ClassFormatException | IOException e) {log.debug(sm.getString("contextConfig.invalidSciHandlesTypes",className), e);}......}

现在总结如下: 在Tomcat存在common、cataina、shared三个公共的classloader,默认情况下,这三个classloader其实是同一个,都是common classloader,而针对每个webapp,也就是context(对应代码中的StandardContext类),都有自己的WebappClassLoader实例来加载每个应用自己的类,该类加载的父类加载器就是是Shared ClassLoader。这样前面关于tomcat的类加载层次应该就清楚起来了。

delegate属性与双亲委派

在context.xml文件中可以配置delegate属性,以用来控制Webappx类加载器的类加载机制。delegate属性默认是false。

当delegate为true时webappx的类加载顺序如下:

  • JVM 的引导类
  • 系统类加载器类
  • 通用类加载器类
  • /WEB-INF/ Web 应用程序的类
  • /WEB-INF/lib/*.jar您的 Web 应用程序

delegate为false时webappx的类加载顺序如下:

  • JVM 的引导类
  • /WEB-INF/ Web 应用程序的类
  • /WEB-INF/lib/*.jar您的 Web 应用程序
  • 系统类加载器类(如上所述)
  • 通用类加载器类(如上所述)

让我再来简单回忆一下JVM的双亲委派模型,在JVM中一个类的加载首先使用其父类加载器去加载,如果加载不到在使用自身的加载器去加载。

 

 

我们以Tomcat的类加载器结构为例,当delegate属性是true时,加载一个自定义servlet是从根加载器,然后是系统类加载器一步步找下来的,这一过程与JVM的双亲委派模型是一致的。但是当delegate为false时却不然,当delegate为fasle时首先依旧从根加载器加载类文件,但是第二步是从webappx类加载器中加载类文件,然后是系统类加载器,最后才是通用类加载器,这与标准的JVM模型并不一致,我们也可以说此时Tomcat打破了双亲委派模型。

Tomcat默认打破了双亲委派,这样的好处之一是当我们在Tomcat中部署多个应用时,即使这些应用程序依赖同一个第三方类库,虽然其版本不同但并不会相互影响。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_379888.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java概述

从项目到代码找工作前的整个学习体系(学会这些东西去解决问题,不单单去学这些东西)JavaSE知识图Java语言跨平台原理 Java语言特点完全面向对象:Java支持封装,继承,多态,面向对象编程,让程序更好的达到高内聚,低耦合的标准。 支持分布式: 健壮型:Java强类型机制,异常…

【Oracle partition by 实现字段去重】

1.利用开窗函数可以实现字段分组去重,其中要注意几点:需要外套查询语句,不然无法识别ROWNUM ,也就是 row_number 注意 分组排序方式,比如这里排序是 s_id 降序排列,以s_code 进行分组,如果有两行数据的 s_code 相同,那么rownum =1 的时候就 是只取第一组,即 s_id最大的…

Vulnhub | DC-4

信息搜集 通过mac地址找真实IP地址 真实IP为192.168.0.151,开放端口80,22 爆破密码 访问后是一个登录框,爆破尝试 admin/happy访问后是一个登录框,爆破尝试 admin/happy 命令执行 登陆后有一个命令执行选项,抓包修改参数可以执行任意命令 反弹shell失败了 找一找敏感文件 ca…

PowerShell教程 - 编程结构(Program Struct)- 第三部分

更新记录 转载请注明出处。 2022年8月21日 发布。 2022年8月18日 从笔记迁移到博客。预定义变量 预定义的布尔值 $True $False预定义变量 预定义变量 描述(Description) $^ 表示当前会话的使用过的最后一条命名的最前部分 $$ 表示当前会话的使用过的最后一…

压测工具 Locust

Locust是一款易于使用的分布式负载测试工具,完全基于事件,即一个locust节点也可以在一个进程中支持数千并发用户,不使用回调,通过gevent使用轻量级过程(即在自己的进程内运行)一、认识Locust 定义 Locust是一款易于使用的分布式负载测试工具,完全基于事件,即一个locust…

#{}和${}的区别是什么

#{}和${}的区别是什么 动态 sql 是 MyBatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 MyBatis 会对其进行动态解析。MyBatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。 区别 1)#{}是预编译处理,$ {}是字符串替换。 2)MyBatis在处理#…

Mybatis组件介绍

核心组件 SqlSessionFactoryBuilder SqlSessionFactoryBuilder的作用就是通过XML或者Java代码来建造一个工厂(SqlSessionFactory),并且可以通过它建造多个这样的工厂。一旦完成建造工厂的任务,我们就应该废弃它,回收空间。所以它的生命周期只存在方法局部,完成工厂的建造即…

JAVA入门2022年8月19日

第一节 1.注释是什么写在程序中对程序进行解释说明的文字。 2.java中书写注释的方法有几种,各自有什么不同// 单行注释/* */ 多行注释/** */ 文档注释 3.注释有什么特点不进行编译,不影响程序的执行 4.注释的快捷键是怎么样的 第二节1.字面…

vue的生命周期

一、Vue 的生命周期 一、Vue 的生命周期流程图二、Vue 生命周期的具体    生命周期 描述beforeCreate 组件实例被创建之初created 组件实例已经完成创建beforeMount 组件挂载之前mounted 组件挂载到实例上去之后beforeUpdate 组件数据发生变化,更新之前updated 组件数据更新…

spring源码学习笔记1——解析xml生成BeanDefinition的过程解析

spring源码学习笔记1——解析xml生成BeanDefinition的过程解析 一丶Spring解析Xml生成BeanDefinition的流程 1.指定xml路径 解析xml首先需要知道xml的位置,如下我们构造了ApplicationContext ApplicationContext context =new ClassPathXmlApplicationContext("bean.xml&…

IOC

介绍 什么是SpringIOC,就是把每一个bean(实体类)与bean(实体类)之间的关系交给第三方容器进行管理。关键类BeanFactory IOC的顶层容器,描述了IOC的规范。 BeanFactory是一个接口,是Spring中工厂的顶层规范,IOC的核心接口。 定义了getBean()、containsBean()等管理Bean的通用…

JUC进阶

JUC进阶 wait和sleep的区别sleep是Thread的静态方法,wait是Object方法sleep不会释放锁,它也不需要占用锁,wait会释放锁但调用它的前提是当前线程占有锁wait必须在同步代码块中Lock锁public class LockTest { public static void main(String[] args) { Ticket t…

Spring 03: 基于xml的构造方法注入

构造方法注入具体有3种注入方式:通过构造方法的 a.参数名称注入 b.参数下标注入 c.默认参数顺序注入参数名称注入School实体类package com.example.pojo03;public class School {private String name;private String address;@Overridepublic String toString() {return &…

Vmware 安装CentOS 7

Vmware 安装CentOS 7 创建虚拟机 1、新建虚拟机,选择自定义(高级),下一步。其他默认下一步。选择操作系统Linux,CentOS 7 64位,下一步。输入主机名称,虚拟机存储位置。 2、输入内核数量3、输入内存大小,下一步,其他默认下一步。4、指定磁盘大小,下一步5、自定义硬件,…

深度学习基础课:课程介绍

大家好~我开设了“深度学习基础班”的线上课程,带领同学从0开始学习全连接和卷积神经网络,进行数学推导,并且实现可以运行的Demo程序 本文为第一节课:课程介绍的复盘文章深度学习基础课:课程介绍 大家好~我开设了“深度学习基础班”的线上课程,带领同学从0开始学习全连接…

一台服务器​最大并发 TCP 连接数多少

一台服务器​最大并发 TCP 连接数多少 入门小站 入门小站 2022-07-06 22:10 发表于湖北收录于合集#Linux485个 #tcp4个首先,问题中描述的65535个连接指的是客户端连接数的限制。在tcp应用中,server事先在某个固定端口监听,client主动发起连接,经过三路握手后建立tcp连接。那…

js的原型

prototype 概述:所有的函数都拥有一个属性 这个属性称为prototype 他是一个对象空间(里面就可以存放对应的数据)他被称为显式原型从上述代码 大家可以看到对应的构造函数的prototype和对应的实例对象的 __proto__ 是相等,那么也就证明了对应俩个内容其实是一个对象。那么我…

Condition介绍

Condition Condition是一种多线程通信工具,表示多线程下参与数据竞争的线程的一种状态,主要负责多线程环境下对线程的挂起和唤醒工作。 方法 // ========== 阻塞 ========== // 造成当前线程在接到信号或被中断之前一直处于等待状态。 void await() throws InterruptedExcept…

解决goland在mac m1下无法调试问题

背景 新电脑mac m1 goland调试抛出异常 异常信息 第一次异常信息 could not launch process: can not run under Rosetta, check that the installed build of Go is right for your CPU architecture 原因是goland版本安装错了. 下载地址:https://www.jetbrains.com/zh-cn/go…

排序(上)

目录冒泡排序(Bubble Sort)插入排序(Insertion Sort)选择排序(Selection Sort)冒泡排序和插入排序的比较 最经典的、最常用的有:冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序 冒泡排序(Bubble Sort) 冒泡排序只会操作相邻的两个数据…