从源码级深入剖析Tomcat类加载原理
众所周知,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类加载器打破了双亲委派机制,自定义类加载类的顺序:
- 扩展类加载器(ExtClassLoader)加载
- Web应用程序类加载器(WebappClassLoader)
- 系统类加载器类(AppClassLoader)
- 公共类加载器类(CommonClassLoader)
如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:
- 扩展类加载器(ExtClassLoader)加载
- 系统类加载器类(AppClassLoader)
- 公共类加载器类(CommonClassLoader)
- 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类加载原理的更多相关文章
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- MapReduce的ReduceTask任务的运行源码级分析
MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...
- MapReduce的MapTask任务的运行源码级分析
TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...
- TaskTracker任务初始化及启动task源码级分析
在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...
- 创作gtk源码级vim帮助文档 tags
创作gtk源码级vim帮助文档 tags 缘由 那只有看到源码了.在linux源码上有个网站 http://lxr.linux.no /+trees, 可以很方面的查出相应版本的代码实现,gtk没有. ...
- 源码级调试的XNU内核
i春秋翻译小组-FWorldCodeZ 源码级调试的XNU内核 无论你是在开发内核扩展,进行漏洞研究,还是还有其他需要进入macOS / iOS内核,XNU,有时你需要附加调试器.当你这样做时,使用源 ...
- 源码级强力分析hadoop的RPC机制
分析对象: hadoop版本:hadoop 0.20.203.0 必备技术点: 1. 动态代理(参考 :http://weixiaolu.iteye.com/blog/1477774 )2. Java ...
- BCB6 如何跨工程(Project)进行源码级调试
如何跨工程(Project)进行源码级调试 在日常工作中,如何跨工程(Project)进行源码级调试这是个无法回避的问题.例如:一个应用程序工程为“prj_A”,一个动态库工程为“prj_B”,“pr ...
- MyBatis 源码篇-MyBatis-Spring 剖析
本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...
- 关于JAVA中源码级注解的编写及使用
一.注解简介: 1.1.什么是"注解": 在我们编写代码时,一定看到过这样的代码: class Student { private String name; @Override ...
随机推荐
- 快来玩AI画图!StableDiffusion模型搭建与使用入门~
前言 最近AI很火,先是AI画图,然后就ChatGPT,后者我已经用了一段时间了,用来写作文挺不错的,但OpenAI屏蔽了中国IP,加上用户太多啥的,用起来没那么爽,但没办法全球只此一家,只能捏着鼻子 ...
- 移动端网页--better-scroll介绍
移动端网页--better-scroll介绍 Options 起始位置及滚动方向 startX:0 开始时的X轴位置 startY:0 开始时的Y轴位置 scrollY: true 滚动方向为 Y 轴 ...
- 在react中使用wangEditorV5
wangEditor是基于JavaScript和css的一款web富文本编辑器,是国内比较好用的一款轻量级富文本编辑器,上手简单,易用且开源免费. 官方文档:http://www.wangeditor ...
- python入门教程之十四面向对象
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的"对象",每个对象都拥有相同的 ...
- 基于SpringBoot实现单元测试的多种情境/方法(二)
本文分享自天翼云开发者社区@<基于SpringBoot实现单元测试的多种情境/方法(二)>, 作者:才开始学技术的小白 1 Mock基础回顾 在上一篇分享中我们详细介绍了简单的.用moc ...
- 如何在2023年学习React
在2023年学习React并不是一件容易的事情.自2019年React Hooks发布以来,我们已经拥有了很多稳定性,但现在形势正在再次变化.而这次变化可能比使用React Hooks时更加不稳定.在 ...
- C++ 测试框架 GoogleTest 初学者入门篇 丙
theme: channing-cyan *以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRno ...
- ditto 添加统计粘贴次数功能
通过观察ditto的表发现, 可以添加触发器进行统计粘贴次数的功能,不需要用源码 Ditto 是一款强大的 Windows 剪贴板增强工具,它支持64位操作系统,而且完全免费,绿色开源,支持中文,而且 ...
- 发现Mysql的主从数据库没有同步,差点凉凉了
摘要:今天发现Mysql的主从数据库没有同步,瞬间整个人头皮发麻. 本文分享自华为云社区<糟了,生产环境数据竟然不一致,人麻了!>,作者:冰 河 . 今天发现Mysql的主从数据库没有同步 ...
- MyQR--生成个性二维码
1.二维码定义: 二维码(2-Dimensional Bar Code),是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的.它是指在一维条码的基础上扩展出另一 ...