众所周知,Java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个JVM中是不能加载相同类库的不同版本的类。
然而与许多服务器应用程序一样,Tomcat 允许容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库,这就要求Tomcat必须打破这种双亲委派机制,通过实现自定义的类加载器(即实现了java.lang.ClassLoader)进行类的加载。下面,就让我们来看看Tomcat类加载原理是怎样的。

Tomcat中有两个最重要的类加载器,第一个便是负责Web应用程序类加载的WebappClassLoader,另一个便是JSP Servlet类加载器`JasperLoader。

Web应用程序类加载器(WebappClassLoader)

上代码:

public class WebappClassLoader extends WebappClassLoaderBase {
public WebappClassLoader() {
super();
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
...
}

我们来看看WebappClassLoader继承的WebappClassLoaderBase中实现的类加载方法loadClass

public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
... 省略不需要关注的代码
protected WebappClassLoaderBase() { super(new URL[0]);
// 获取当前WebappClassLoader的父加载器系统类加载器
ClassLoader p = getParent();
if (p == null) {
p = getSystemClassLoader();
}
this.parent = p;
// javaseClassLoader变量经过以下代码的执行,
// 得到的是扩展类加载器(ExtClassLoader)
ClassLoader j = String.class.getClassLoader();
if (j == null) {
j = getSystemClassLoader();
while (j.getParent() != null) {
j = j.getParent();
}
}
this.javaseClassLoader = j; securityManager = System.getSecurityManager();
if (securityManager != null) {
refreshPolicy();
}
} ...省略不需要关注的代码
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null; // Web应用程序停止状态时,不允许加载新的类
checkStateForClassLoading(name); // 如果之前加载过该类,就可以从Web应用程序类加载器本地类缓存中查找,
// 如果找到说明WebappClassLoader之前已经加载过这个类
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
} // Web应用程序本地类缓存中没有,可以从系统类加载器缓存中查找,
// 如果找到说明AppClassLoader之前已经加载过这个类
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
} // 将类似java.lang.String这样的类名这样转换成java/lang/String
// 这样的资源文件名
String resourceName = binaryNameToPath(name, false);
// 获取引导类加载器(BootstrapClassLoader)
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// 引导类加载器根据转换后的类名获取资源url,如果url不为空,就说明找到要加载的类
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) {
ExceptionUtils.handleThrowable(t);
tryLoadingFromJavaseLoader = true;
} // 首先,从扩展类加载器(ExtClassLoader)加载,防止Java核心API库被Web应用程序类随意篡改
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
} // 当使用安全管理器时,允许访问这个类
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
/*
* 如果Web应用程序类加载器配置为,<Loader delegate="true"/> 或者满足下列条件的类:
* 当前类属于以下这些jar包中:
* annotations-api.jar — Common Annotations 1.2 类。
* catalina.jar — Tomcat 的 Catalina servlet 容器部分的实现。
* catalina-ant.jar — 可选。用于使用 Manager Web 应用程序的 Tomcat Catalina Ant 任务。
* catalina-ha.jar — 可选。提供基于 Tribes 构建的会话集群功能的高可用性包。
* catalina-storeconfig.jar — 可选。从当前状态生成 XML 配置文件。
* catalina-tribes.jar — 可选。高可用性包使用的组通信包。
* ecj-*.jar — 可选。Eclipse JDT Java 编译器用于将 JSP 编译为 Servlet。
* el-api.jar — 可选。EL 3.0 API。
* jasper.jar — 可选。Tomcat Jasper JSP 编译器和运行时。
* jasper-el.jar — 可选。Tomcat EL 实现。
* jaspic-api.jar — JASPIC 1.1 API。
* jsp-api.jar — 可选。JSP 2.3 API。
* servlet-api.jar — Java Servlet 3.1 API。
* tomcat-api.jar — Tomcat 定义的几个接口。
* tomcat-coyote.jar — Tomcat 连接器和实用程序类。
* tomcat-dbcp.jar — 可选。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的
* 包重命名副本的数据库连接池实现。
* tomcat-i18n-**.jar — 包含其他语言资源包的可选 JAR。由于默认包也包含在每个单独的JAR
* 中,如果不需要消息国际化,可以安全地删除它们。
* tomcat-jdbc.jar — 可选。另一种数据库连接池实现,称为 Tomcat JDBC 池。有关详细信息,请参阅 文档。
* tomcat-jni.jar — 提供与 Tomcat Native 库的集成。
* tomcat-util.jar — Apache Tomcat 的各种组件使用的通用类。
* tomcat-util-scan.jar — 提供 Tomcat 使用的类扫描功能。
* tomcat-websocket.jar — 可选。Java WebSocket 1.1 实现
* websocket-api.jar — 可选。Java WebSocket 1.1 API
*
* 此处的filter方法,实际上tomcat官方将filter类加载过滤条件,看作是一种类加载器,
* 将其取名为CommonClassLoader
*/
boolean delegateLoad = delegate || filter(name, true); // 如果ExtClassLoader没有获取到,说明是非JRE核心类,那么就从系统类加载器(也称AppClassLoader
// 应用程序类加载器)加载
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
}
} // 从Web应用程序的类加载器(也就是WebappClassLoader)中加载类。Web应用程序的类加载器是
// 一个特殊的类加载器,它负责从Web应用程序的本地库中加载类
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
} // 经过上面几个步骤还未加载到类,则采用系统类加载器(也称应用程序类加载器)进行加载
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);
}
...省略不需要关注的代码
}

综上所述,我们得出WebappClassLoader类加载器打破了双亲委派机制,自定义类加载类的顺序:

  1. 扩展类加载器(ExtClassLoader)加载
  2. Web应用程序类加载器(WebappClassLoader)
  3. 系统类加载器类(AppClassLoader)
  4. 公共类加载器类(CommonClassLoader)

如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:

  1. 扩展类加载器(ExtClassLoader)加载
  2. 系统类加载器类(AppClassLoader)
  3. 公共类加载器类(CommonClassLoader)
  4. Web应用程序类加载器(WebappClassLoader)

JSP类加载器(JasperLoader)

上代码:

public class JasperLoader extends URLClassLoader {

    private final PermissionCollection permissionCollection;
private final SecurityManager securityManager; // JSP类加载器的父加载器是Web应用程序类加载器(WebappClassLoader)
public JasperLoader(URL[] urls, ClassLoader parent,
PermissionCollection permissionCollection) {
super(urls, parent);
this.permissionCollection = permissionCollection;
this.securityManager = System.getSecurityManager();
} @Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} @Override
public synchronized Class<?> loadClass(final String name, boolean resolve)
throws ClassNotFoundException { Class<?> clazz = null; // 从JVM的类缓存中查找
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
} // 当使用SecurityManager安全管理器时,允许访问访类
if (securityManager != null) {
int dot = name.lastIndexOf('.');
if (dot >= 0) {
try {
// Do not call the security manager since by default, we grant that package.
if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
securityManager.checkPackageAccess(name.substring(0,dot));
}
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
se.printStackTrace();
throw new ClassNotFoundException(error);
}
}
}
// 如果类名不是以org.apache.jsp包名开头的,则采用WebappClassLoader加载
if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
// Class is not in org.apache.jsp, therefore, have our
// parent load it
clazz = getParent().loadClass(name);
if( resolve ) {
resolveClass(clazz);
}
return clazz;
}
// 如果是org.apache.jsp包名开头JSP类,就调用父类URLClassLoader的findClass方法
// 动态加载类文件,解析成Class类,返回给调用方
return findClass(name);
}
}

下面是URLClassLoader的findClass方法,具体实现:

protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 解析类的字节码文件生成Class类对象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}

从源码中我们可以看到,JSP类加载原理是,先从JVM类缓存中(也就是Bootstrap类加载器加载的类)加载,如果不是核心类库的类,就从Web应用程序类加载器WebappClassLoader中加载,如果还未找到,就说明是jsp类,则通过动态解析jsp类文件获得要加载的类。

经过上面两个Tomcat核心类加载器的剖析,我们也就知道了Tomcat类的加载原理了。
下面我们来总结一下:Tomcat会为每个Web应用程序创建一个WebappClassLoader类加载器进行类的加载,不同的类加载器实例加载的类是会被认为是不同的类,即使它们的类名相同,这样的话就可以实现在同一个JVM下,允许Tomcat容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库。
针对JSP类,会由专门的JSP类加载器(JasperLoader)进行加载,该加载器会针对JSP类在每次加载时都会解析类文件,Tomcat容器会启动一个后台线程,定时检测JSP类文件的变化,及时更新类文件,这样就实现JSP文件的热加载功能。

从源码级深入剖析Tomcat类加载原理的更多相关文章

  1. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  2. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  3. MapReduce的MapTask任务的运行源码级分析

    TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...

  4. TaskTracker任务初始化及启动task源码级分析

    在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...

  5. 创作gtk源码级vim帮助文档 tags

    创作gtk源码级vim帮助文档 tags 缘由 那只有看到源码了.在linux源码上有个网站 http://lxr.linux.no /+trees, 可以很方面的查出相应版本的代码实现,gtk没有. ...

  6. 源码级调试的XNU内核

    i春秋翻译小组-FWorldCodeZ 源码级调试的XNU内核 无论你是在开发内核扩展,进行漏洞研究,还是还有其他需要进入macOS / iOS内核,XNU,有时你需要附加调试器.当你这样做时,使用源 ...

  7. 源码级强力分析hadoop的RPC机制

    分析对象: hadoop版本:hadoop 0.20.203.0 必备技术点: 1. 动态代理(参考 :http://weixiaolu.iteye.com/blog/1477774 )2. Java ...

  8. BCB6 如何跨工程(Project)进行源码级调试

    如何跨工程(Project)进行源码级调试 在日常工作中,如何跨工程(Project)进行源码级调试这是个无法回避的问题.例如:一个应用程序工程为“prj_A”,一个动态库工程为“prj_B”,“pr ...

  9. MyBatis 源码篇-MyBatis-Spring 剖析

    本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...

  10. 关于JAVA中源码级注解的编写及使用

    一.注解简介: 1.1.什么是"注解": ​ 在我们编写代码时,一定看到过这样的代码: class Student { private String name; @Override ...

随机推荐

  1. Rancher(V2.6.3)安装K8s教程

    Rancher(V2.6.3)安装K8s教程 一,安装前环境准备: 1,升级Linux服务器内核 Ubuntu20.04: #查看当前内核版本 uname -rs #查看软件库中可下载的内核 sudo ...

  2. MySQL MHA信息的收集【Filebeat+logstash+MySQL】

    一.项目背景 随着集团MHA集群的日渐增长,MHA管理平台话越来越迫切.而MHA平台的建设第一步就是将这些成百上千套的MHA集群信息收集起来,便于查询和管理. MHA主要信息如下: (1)基础配置信息 ...

  3. 数组描述线性表(C++实现)

    线性表也称有序表,其每一个实例都是元素的一个有序集合 抽象类linearList 一个抽象类包含没有实现代码的成员函数,这样的成员函数称为纯虚函数,用数字0作为初始值来说明 template<c ...

  4. 轻量化3D文件格式转换HOOPS Exchange新特性

    BIM与AEC市场发展现状 近年来BIM(建筑信息模型)和AEC(建筑.工程和施工)市场一直保持着持续增长.2014 年全球 BIM 软件市场价值 27.6 亿美元,而到 2022年,预期到达115. ...

  5. Vue中实现数据列表无缝轮播

    类似这种滚动轮播效果 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta char ...

  6. TOF和结构光

    文章目录 TOF和结构光 一.ToF 二.结构光 三.测量距离.分辨率.开发周期的对比 TOF和结构光 一.ToF ToF(Time of Flight)飞行时间 字面理解就是通过光的飞行时间来计算距 ...

  7. Linux期末佛脚指南

    Linux的期末佛脚复习 常用文件操作命令 touch (创建文件) cat (查看文件内容) head (查看文件开头) tail (查看文件结尾) cp (复制文件) mv (移动文件) ls ( ...

  8. 波场(Tron) 网页版钱包开源

    之前做区块链项目太难了,很多组件.工具没有开源项目,需要自己写很麻烦. 我整理了几个自己给公司开发项目的时候,分离出来的几个工具,已经上传到 Gihub 了,感觉浏览量还行,在这里给园子里的朋友分享下 ...

  9. Llinux系统(Centos/Ubuntu/Debian)弹性云数据盘home扩容|云盘一键分扩容

    一.脚本自动处理 适用:数据盘home分区升级扩容合并.云盘升级扩容合并.(注意:不要在宝塔面板终端执行) 输入以下命令执行:  wget -O homeV31.sh http://downinfo. ...

  10. 从浏览器输入域名开始分析DNS解析过程

    摘要:DNS(Domain Name System)是域名系统的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,用于 TCP/IP 网络. 本文分享自华为云社区<DNS那些事--从浏 ...