博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
中路径查找器的功能_死磕Tomcat系列(4)——Tomcat中的类加载器
阅读量:5899 次
发布时间:2019-06-19

本文共 5967 字,大约阅读时间需要 19 分钟。

点击上方“Java技术前线”,选择“置顶或者星标”

与你一起成长

在学习Tomcat中的类加载器,并且Tomcat为什么要实现自己的类加载器打破双亲委派模型原因之前,我们首先需要知道Java中定义的类加载器是什么,双亲委派模型是什么。

Java中的类加载器

类加载器负责在程序运行时将java文件动态加载到JVM中

从Java虚拟机的角度来讲的话,存在两种不同的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):这个类加载器是使用C++语言实现的,是虚拟机自身的一部分。

  • 其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader,其中其他类加载器大概又分为

    • ExtensionClassLoader:这个类加载器由 ExtClassLoader实现,它负责加载 JAVA_HOME/lib/ext目录中的所有类,或者被 java.ext.dir系统变量所指定的路径中所有的类。

    • ApplicationClassLoader:这个类加载器是由 AppClassLoader实现的,它负责加载用户类路径(ClassPath)上所指定的所有类,如果应用中没有自定义自己的类加载器,那么一般情况就是程序中默认的类加载器。

    • 自定义加载器:根据自己需求,自定义加载特定路径的加载器。

4a84fd871d18c9c366306814965b77a8.png

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性

双亲委派模型

上图中展示的层次结构,称之为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其他加载器都应该有自己的父加载器。这里的父子关系不是通过继承来实现的,而是通过设置 parent变量来实现的。

双亲委派模型工作过程是:如果收到一个类加载的请求,本身不会先加载此类,而是会先将此请求委派给父类加载器去完成,每个层次都是如此,直到启动类加载器中,只有父类都没有加载此文件,那么子类才会尝试自己去加载。

为什么要设置双亲委派模型呢?其实是为了保证Java程序的稳定运行,例如Object类,它是存放在 rt.jar中,无论哪一个类加载器要加载Object类,最终都会委托给顶层的BootStrapClassLoader,所以所有的类中使用的Object都是同一个类,相反如果没有双亲委派模型的话,那么随意一个类加载器都可以定义一个新的Object类,那么应用程序将会变得非常混乱。其实双亲委派模型代码非常简单。实现在ClassLoader中的loadClass方法下。

 

protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// 首先,检查请求类是否被加载过

Class> c = findLoadedClass(name);

if (c == null) {

long t0 = System.nanoTime();

try {

// 如果没被本类类加载器加载过,先委托给父类进行加载

if (parent != null) {

c = parent.loadClass(name, false);

} else {

// 如果没有父类,则表明在顶层,就交给BootStrap类加载器加载

c = findBootstrapClassOrNull(name);

}

// 如果最顶层的类也找不到,那么就会抛出ClassNotFoundException异常

} catch (ClassNotFoundException e) {

}

// 如果父类都没有加载过此类,子类才开始加载此类

if (c == null) {

c = findClass(name);

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

}

protected Class> findClass(String name) throws ClassNotFoundException {

throw new ClassNotFoundException(name);

}

复制代码

我们可以看到 findClass方法是需要子类自己去实现的逻辑。

Tomcat中的类加载器

下面的简图是Tomcat9版本的官方文档给出的Tomcat的类加载器的图。

 

Bootstrap

|

System

|

Common

/ \

Webapp1 Webapp2 ..

复制代码

  • Bootstrap :是Java的最高的加载器,用C语言实现,主要用来加载JVM启动时所需要的核心类,例如 $JAVA_HOME/jre/lib/ext路径下的类。

  • System:会加载 CLASSPATH系统变量所定义路径的所有的类。

  • Common:会加载Tomcat路径下的lib文件下的所有类。

  • Webapp1、Webapp2……:会加载webapp路径下项目中的所有的类。一个项目对应一个WebappClassLoader,这样就实现了应用之间类的隔离了。

这3个部分,在上面的Java双亲委派模型图中都有体现。不过可以看到ExtClassLoader没有画出来,可以理解为是跟bootstrap合并了,都是去 JAVA_HOME/jre/lib下面加载类。那么Tomcat为什么要自定义类加载器呢?

  • 隔离不同应用:部署在同一个Tomcat中的不同应用A和B,例如A用了Spring2.5。B用了Spring3.5,那么这两个应用如果使用的是同一个类加载器,那么Web应用就会因为jar包覆盖而无法启动。

  • 灵活性:Web应用之间的类加载器相互独立,那么就可以根据修改不同的文件重建不同的类加载器替换原来的。从而不影响其他应用。

  • 性能:如果在一个Tomcat部署多个应用,多个应用中都有相同的类库依赖。那么可以把这相同的类库让Common类加载器进行加载。

Tomcat自定义了WebAppClassLoader类加载器。打破了双亲委派的机制,即如果收到类加载的请求,会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载Web应用自己定义的类。我们知道ClassLoader默认的loadClass方法是以双亲委派的模型进行加载类的,那么Tomcat既然要打破这个规则,就要重写loadClass方法,我们可以看WebAppClassLoader类中重写的loadClass方法。

 

@Override

public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {

synchronized (getClassLoadingLock(name)) {

Class> clazz = null;

// 1. 从本地缓存中查找是否加载过此类

clazz = findLoadedClass0(name);

if (clazz != null) {

if (log.isDebugEnabled())

log.debug(" Returning class from cache");

if (resolve)

resolveClass(clazz);

return clazz;

}

// 2. 从AppClassLoader中查找是否加载过此类

clazz = findLoadedClass(name);

if (clazz != null) {

if (log.isDebugEnabled())

log.debug(" Returning class from cache");

if (resolve)

resolveClass(clazz);

return clazz;

}

String resourceName = binaryNameToPath(name, false);

// 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类

ClassLoader javaseLoader = getJavaseClassLoader();

boolean tryLoadingFromJavaseLoader;

try {

URL url;

if (securityManager != null) {

PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);

url = AccessController.doPrivileged(dp);

} else {

url = javaseLoader.getResource(resourceName);

}

tryLoadingFromJavaseLoader = (url != null);

} catch (Throwable t) {

tryLoadingFromJavaseLoader = true;

}

boolean delegateLoad = delegate || filter(name, true);

// 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类

if (delegateLoad) {

if (log.isDebugEnabled())

log.debug(" Delegating to parent classloader1 " + parent);

try {

clazz = Class.forName(name, false, parent);

if (clazz != null) {

if (log.isDebugEnabled())

log.debug(" Loading class from parent");

if (resolve)

resolveClass(clazz);

return clazz;

}

} catch (ClassNotFoundException e) {

// Ignore

}

}

// 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载

if (log.isDebugEnabled())

log.debug(" Searching local repositories");

try {

clazz = findClass(name);

if (clazz != null) {

if (log.isDebugEnabled())

log.debug(" Loading class from local repository");

if (resolve)

resolveClass(clazz);

return clazz;

}

} catch (ClassNotFoundException e) {

// Ignore

}

// 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载

if (!delegateLoad) {

if (log.isDebugEnabled())

log.debug(" Delegating to parent classloader at end: " + parent);

try {

clazz = Class.forName(name, false, parent);

if (clazz != null) {

if (log.isDebugEnabled())

log.debug(" Loading class from parent");

if (resolve)

resolveClass(clazz);

return clazz;

}

} catch (ClassNotFoundException e) {

// Ignore

}

}

}

throw new ClassNotFoundException(name);

}

复制代码

最后借用Tomcat官网上的话总结:

Web应用默认的类加载顺序是(打破了双亲委派规则):

  1. 先从JVM的BootStrapClassLoader中加载。

  2. 加载Web应用下 /WEB-INF/classes中的类。

  3. 加载Web应用下 /WEB-INF/lib/*.jap中的jar包中的类。

  4. 加载上面定义的System路径下面的类。

  5. 加载上面定义的Common路径下面的类。

如果在配置文件中配置了 delegate="true"/>,那么就是遵循双亲委派规则,加载顺序如下:

  1. 先从JVM的BootStrapClassLoader中加载。

  2. 加载上面定义的System路径下面的类。

  3. 加载上面定义的Common路径下面的类。

  4. 加载Web应用下 /WEB-INF/classes中的类。

  5. 加载Web应用下 /WEB-INF/lib/*.jap中的jar包中的类。

参考文章

  • tomcat.apache.org/tomcat-9.0-…

  • 深入理解Java虚拟机

  • 深入拆解Tomcat

  • kyfxbl.iteye.com/blog/170723…

作者:不学无数的程序员

来源:https://juejin.im/post/5d1efd96f265da1bd04f0032

热门内容:

  • 888G面试资源分享
  • Mybatis教程1:MyBatis快速入门
  • MyBatis教程2:使用MyBatis对表执行CRUD操作
  • MyBatis教程3:优化MyBatis配置文件中的配置
  • MyBatis教程4:解决字段名与实体类属性名不相同的冲突
  • 2019年Java经典面试题汇总
  • Maven教程1:Maven入门
  • Maven教程2:Maven项目构建过程练习
  • Maven教程3:使用Maven构建项目
  • Maven教程4:Maven核心概念
  • Maven教程5: 聚合与继承
  • Maven教程6: Maven与Eclipse整合
  • Maven教程7:eclipse中使用Maven创建Web项目.md
  • Maven教程8: 使用Maven构建多模块项目
  • Maven教程9: 使用Nexus搭建Maven私服

84cbcc9e5840e9022978daa06a8f7a53.png

喜欢就点个"在看"呗^_^

转载地址:http://feqsx.baihongyu.com/

你可能感兴趣的文章
react 取消 eslint
查看>>
【11】ajax请求后台接口数据与返回值处理js写法
查看>>
Python菜鸟之路:Jquery Ajax的使用
查看>>
LeetCode算法题-Maximum Depth of Binary Tree
查看>>
sha1withRSA算法
查看>>
Vim和操作系统剪贴板交互
查看>>
Cox 教学视频5
查看>>
JVM类加载(4)—加载器
查看>>
public/private/protected的具体区别
查看>>
面试宝典——求一个字符串中连续出现次数最多的子串
查看>>
VMware Workstation虚拟机上网设置
查看>>
Jenkins持续集成学习-搭建jenkins问题汇总
查看>>
C#Note13:如何在C#中调用python
查看>>
Android介绍以及源码编译---Android源码下载
查看>>
SpringBoot集成redis缓存
查看>>
sql经典语句
查看>>
使用ffmpeg实现对h264视频解码 -- (实现了一个易于使用的c++封装库)
查看>>
第4周作业-面向对象设计与继承
查看>>
机器学习的原理
查看>>
flink watermark介绍
查看>>