Tomcat 源码分析(三)-(三)-自动加载类及检测文件变动原理

2024-04-06 08:48

本文主要是介绍Tomcat 源码分析(三)-(三)-自动加载类及检测文件变动原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Tomcat 源码分析(三)-WEB加载原理(三)

文章目录

  • Tomcat 源码分析(三)-WEB加载原理(三)
    • @[toc]
    • Tomcat 7 自动加载类及检测文件变动原理
      • 关于开发工具中的自动加载
      • 分析Tomcat自动加载的实现
      • 检测文件变动分析
        • WebappLoader 的初始化
        • WebappClassLoader 的 modified 方法-检测变动的代码
        • 关于当前资源信息获取
        • 关于已加载类的资源信息
      • 结束
    • 参考资料
      • 源码分析三:web 应用加载原理

Tomcat 7 自动加载类及检测文件变动原理

关于开发工具中的自动加载

在常用的web应用开发工具(如 Eclipse、IntelJ )中都有集成Tomcat,这样可以将开发的web项目直接发布到tomcat中去。这里会遇到一种情况,在修改了一个文件后,开发工具可以直接编译class文件发布到tomcat的web工程里面。如果tomcat没有配置自动加载功能,JVM中的还是就的class,就需要手动进行restart。

所以,这里说一下tomcat提供的配置自动加载的配置属性:

`<Context path="/HelloWorld" docBase="C:/apps/apache-tomcat/DeployedApps/HelloWorld" reloadable="true"/>`

就是reloadable="true"这个属性,这样 Tomcat 就会监控所配置的 web 应用实际路径下的/WEB-INF/classes/WEB-INF/lib两个目录下文件的变动,如果发生变更 tomcat 将会自动重启该应用。

分析Tomcat自动加载的实现

自动加载的实现,先从Tomcat在启动之后会有一个后台线程,

ContainerBackgroundProcessor[StandardEngine[Catalina]]

定时【默认10秒】执行Engine、Host、Context、Wrapper 各容器组件及与它们相关的其它组件的 backgroundProcess 方法。- 这里开始分析。

这个方法被定义在,所有容器组件的父类org.apache.catalina.core.ContainerBase类的 backgroundProcess`方法中:

/**
*执行定期任务,如重新加载等。此方法将
*在该容器的类加载上下文中调用。意外的
*丢弃物将被捕获并记录。*/
@Override
public void backgroundProcess() {if (!getState().isAvailable())return;Cluster cluster = getClusterInternal();if (cluster != null) {try {cluster.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);}}//删减后的↓↓↓↓↓↓↓ 逐个调用内部相关的backgroundProcess()方法Loader loader = getLoaderInternal();loader.backgroundProcess();//**********现在要看看的是上面↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑这个的地方了*******Manager manager = getManagerInternal();manager.backgroundProcess();Realm realm = getRealmInternal();realm.backgroundProcess();//调用管道内左右阀的backgroundProcess()方法Valve current = pipeline.getFirst();while (current != null) {current.backgroundProcess();current = current.getNext();}//最后这里注册了一个Lifecycle.PERIODIC_EVENT事件  之前分析加载web是在这个事件的处理中fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}

这里与自动加载的代码是Loader :Loader loader = getLoaderInternal();,loader.backgroundProcess();这两段。

这里看一下这个loader 变量是什么时候初始化的:【在StandardContext的startInternal 方法中】

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

这里可以看到这里这个设置的Loader 的类是WebappLoader。

然后具体的关联是在Loader的backgroundProcess()中:

//public class WebappLoader extends LifecycleMBeanBase implements Loader, PropertyChangeListener {
@Override
public void backgroundProcess() {if (reloadable && modified()) {try {Thread.currentThread().setContextClassLoader(WebappLoader.class.getClassLoader());if (container instanceof StandardContext) {((StandardContext) container).reload();}} finally {......}} else {closeJARs(false);}
}

这里可以看到,这里的条件是reloadable和modified(),这里的reloadable就是配置Context节点的reloadable属性值,而modified()这个方法是对检查文件变动的,之后会分析。

先来看一下,最终要执行的重新加载的方法:StandardContext类的reload():

public synchronized void reload() {
......// Stop accepting requests temporarily.setPaused(true);try {stop();} catch (LifecycleException e) {.......}try {start();} catch (LifecycleException e) {....... }setPaused(false);
......
}

这里的reload方法中,将执行stop方法将原有的该 web 应用停掉,再调用 start 方法启动该 Context 。

start方法,则会重新加载启动web应用。【就像之前分析的那样_(:з」∠)_】

检测文件变动分析

前面,进行reload重新启动web应用的条件为:if (reloadable && modified()) {,一个为配置值,另一个就是接下来要说的了。- modified()

//public class WebappLoader extends LifecycleMBeanBase  implements Loader, PropertyChangeListener {
public boolean modified() {return classLoader != null ? classLoader.modified() : false ;
}

这里进行判断的的实际方法是:WebappLoader 的实例变量 classLoader 的 modified 方法。

说明个Tomcat中加载器的东东,每个web应用会对一个Context节点,在JVM中就会对应一个org.apache.catalina.core.StandardContext对象,而每一个StandardContext对象内部都一个加载器实例loader实例变量。可以看到前面说明,这个loader实际上是WebappLoader对象。

而每一个 WebappLoader 对象内部关联了一个 classLoader 变量(就这这个类的定义中,可以看到该变量的类型是org.apache.catalina.loader.WebappClassLoader)。

所以,这里一个web应用会对应一个StandardContext 一个WebappLoader 一个WebappClassLoader 。

WebappLoader 的初始化

WebappLoader的初始化在StandardContext 的初始化的时候已经完成了。上文中已有了:

if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);
}
......if ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();

这里要的代码是先初始化了,之后执行了loader的start()方法,因为WebappLoader 本身也是继承了LifecycleBase 类,所以这里的start()方法,最终也会执行到类自定义的startInternal 方法。

WebappLoader.startInternal ()方法的源码:

//public class WebappLoader extends LifecycleMBeanBase implements Loader, PropertyChangeListener {
@Override
protected void startInternal() throws LifecycleException {......// 为JNDI协议注册流处理程序工厂 ?? 啥意思啊 ╮(╯_╰)╭// Register a stream handler factory for the JNDI protocolURLStreamHandlerFactory streamHandlerFactory =DirContextURLStreamHandlerFactory.getInstance();......URL.setURLStreamHandlerFactory(streamHandlerFactory);......}// ********基于当前存储库列表构造类加载器*********需要看的就是这一段// Construct a class loader based on our current repositories listtry {classLoader = createClassLoader();   // 开始就调用了这个创建加载器的方法classLoader.setJarOpenInterval(this.jarOpenInterval);classLoader.setResources(container.getResources());classLoader.setDelegate(this.delegate);classLoader.setSearchExternalFirst(searchExternalFirst);if (container instanceof StandardContext) {classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());classLoader.setClearReferencesRmiTargets(((StandardContext) container).getClearReferencesRmiTargets());classLoader.setClearReferencesStatic(((StandardContext) container).getClearReferencesStatic());classLoader.setClearReferencesStopThreads(((StandardContext) container).getClearReferencesStopThreads());classLoader.setClearReferencesStopTimerThreads(((StandardContext) container).getClearReferencesStopTimerThreads());classLoader.setClearReferencesHttpClientKeepAliveThread(((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());classLoader.setClearReferencesObjectStreamClassCaches(((StandardContext) container).getClearReferencesObjectStreamClassCaches());}for (int i = 0; i < repositories.length; i++) {classLoader.addRepository(repositories[i]);}// Configure our repositoriessetRepositories();setClassPath();setPermissions();((Lifecycle) classLoader).start();// Binding the Webapp class loader to the directory context.....} catch (Throwable t) {......}setState(LifecycleState.STARTING);
}/*** Create associated classLoader. 创建关联的类加载器。* 这里反射实例化了一个WebappClassLoader 对象。*/private WebappClassLoaderBase createClassLoader()throws Exception {Class<?> clazz = Class.forName(loaderClass);  WebappClassLoaderBase classLoader = null;if (parentClassLoader == null) {parentClassLoader = container.getParentClassLoader();}Class<?>[] argTypes = { ClassLoader.class };Object[] args = { parentClassLoader };Constructor<?> constr = clazz.getConstructor(argTypes);classLoader = (WebappClassLoaderBase) constr.newInstance(args);return classLoader;}

这里,就分析了这个要使用的类的初始化过程了。

WebappClassLoader 的 modified 方法-检测变动的代码

可以再前边看到,判断文件变动的检测代码为modified()方法:

classLoader != null ? classLoader.modified() : false

就是这句代码,所以来看一下这个**classLoader.modified()**也就是WebappClassLoader 的:

public boolean modified() {......s// Checking for modified loaded resourcesint length = paths.length;int length2 = lastModifiedDates.length;if (length > length2)length = length2;
//****这里对比资源文件里面的文件的最后修改时间是否一致,以便判断是否变动****for (int i = 0; i < length; i++) {try {long lastModified =((ResourceAttributes) resources.getAttributes(paths[i])).getLastModified();if (lastModified != lastModifiedDates[i]) {......return (true);}} catch (NamingException e) {......return (true);}}length = jarNames.length;// Check if JARs have been added or removedif (getJarPath() != null) {try {NamingEnumeration<Binding> enumeration =resources.listBindings(getJarPath());int i = 0;while (enumeration.hasMoreElements() && (i < length)) {NameClassPair ncPair = enumeration.nextElement();String name = ncPair.getName();// Ignore non JARs present in the lib folderif (!name.endsWith(".jar"))continue;if (!name.equals(jarNames[i])) {// Missing JAR......return (true);}i++;}if (enumeration.hasMoreElements()) {while (enumeration.hasMoreElements()) {NameClassPair ncPair = enumeration.nextElement();String name = ncPair.getName();// Additional non-JAR files are allowedif (name.endsWith(".jar")) {// There was more JARslog.info("    Additional JARs have been added");return (true);}}} else if (i < jarNames.length) {// There was less JARslog.info("    Additional JARs have been added");return (true);}} catch (NamingException e) {.......}}// No classes have been modifiedreturn (false);
}

这段代码从总体上看共分成两部分,第一部分检查 web 应用中的 class 文件是否有变动,根据 class 文件的最近修改时间来比较,如果有不同则直接返回true,如果 class 文件被删除也返回true

第二部分检查 web 应用中的 jar 文件是否有变动,如果有同样返回true

这里的代码看起来,还是比较容易理解的╮(╯_╰)╭

关于当前资源信息获取

关于,检查文件变动的关键代码就是:

 long lastModified =((ResourceAttributes) resources.getAttributes(paths[i])).getLastModified();if (lastModified != lastModifiedDates[i]) {

WebappClassLoader 的实例变量resources中取出文件当前的最近修改时间,与 WebappClassLoader 原来缓存的该文件的最近修改时间做比较。

这里看一下 resources.getAttributes 方法:

这里的resources实际上的是javax.naming.directory.DirContext类,看下初始化的地方,在WebappLoader 的 startInternal 方法中:【就在上面的】

 classLoader.setResources(container.getResources()); //这里设置的,是在StandardContext初始化的时候((Lifecycle) classLoader).start();

**StandardContext 中 resources 是怎么赋值:**StandardContext 的 startInternal 方法中

// Add missing components as necessary
if (webappResources == null) {   // (1) Required by Loadertry {if ((getDocBase() != null) && (getDocBase().endsWith(".war")) &&(!(new File(getBasePath())).isDirectory()))setResources(new WARDirContext()); //我们常用的wer发布加载的是这个elsesetResources(new FileDirContext()); //默认的应用是文件发布的} catch (IllegalArgumentException e) {......ok = false;}
}
if (ok) {if (!resourcesStart()) {...... } //在这里做了初始化
}

这里会对resources进行赋值,并且初始化;看下resourcesStart()初始化的方法:

//public class StandardContext extends ContainerBase
public boolean resourcesStart() {
......try {ProxyDirContext proxyDirContext =new ProxyDirContext(env, webappResources);......// 中间的太多不知道啥的东西 (ノ`Д)ノ super.setResources(proxyDirContext);   //要看的就只是这个} catch (Throwable t) {......}return (ok);
}

很明显,这里的resources 赋的是 proxyDirContext 对象,而 proxyDirContext 是一个代理对象,代理的就是 webappResources ,按上面的描述即org.apache.naming.resources.FileDirContext

org.apache.naming.resources.FileDirContext继承自抽象父类org.apache.naming.resources.BaseDirContext,而 BaseDirContext 又实现了javax.naming.directory.DirContext接口。所以 JNDI 操作中的 lookup、bind、getAttributes、rebind、search 等方法都已经在这两个类中实现了。当然里面还有 JNDI 规范之外的方法如 list 等。

所以,接下来看看一下这个getAttributes 方法的调用

最终都会调用到抽象方法 doGetAttributes 的。

//public abstract class BaseDirContext implements DirContext {
public final Attributes getAttributes(String name, String[] attrIds)throws NamingException {
......// Next do a standard lookupAttributes attrs = doGetAttributes(name, attrIds);

看一下FileDirContext 的doGetAttributes定义:

protected Attributes doGetAttributes(String name, String[] attrIds)throws NamingException {// Building attribute listFile file = file(name, true);if (file == null)         return null;return new FileResourceAttributes(file);
}

到这里就可以了,最终是调用了File的东西【java文件操作】。

实际就是根据传入的文件名查找目录下是否存在该文件,如果存在则返回包装了的文件属性对象 FileResourceAttributes 。 FileResourceAttributes 类实际是对java.io.File类做了一层包装。

关于已加载类的资源信息

还有两个内置变量pathslastModifiedDates值究竟什么时候赋的呢?

说一下 WebappClassLoader 这个自定义类加载器的用法,在 Tomcat 中所有 web 应用内WEB-INF\classes目录下的 class 文件都是用这个类加载器来加载的,一般的自定义加载器都是覆写 ClassLoader 的 findClass 方法,这里也不例外。WebappClassLoader 覆盖的是 URLClassLoader 类的 findClass 方法,而在这个方法内部最终会调用findResourceInternal(String name, String path)方法:

// Register the full path for modification checking
// Note: Only syncing on a 'constant' object is needed
synchronized (allPermission) {int j;long[] result2 =new long[lastModifiedDates.length + 1];for (j = 0; j < lastModifiedDates.length; j++) {result2[j] = lastModifiedDates[j];}result2[lastModifiedDates.length] = entry.lastModified;lastModifiedDates = result2;String[] result = new String[paths.length + 1];for (j = 0; j < paths.length; j++) {result[j] = paths[j];}result[paths.length] = fullPath;paths = result;}

这里可以看到在**加载一个新的 class 文件时会给 WebappClassLoader 的实例变量lastModifiedDatespaths数组添加元素。**这里就解答了上面提到的文件变更比较代码的疑问。要说明的是在 tomcat 启动后 web 应用中所有的 class 文件并不是全部加载的,而是配置在 web.xml 中描述的需要与应用一起加载的才会立即加载,否则只有到该类首次使用时才会由类加载器加载。

而关于 jar 包文件变动的比较代码同 class 文件比较的类似,同样是取出当前 web 应用WEB-INF\lib目录下的所有 jar 文件,与 WebappClassLoader 内部缓存的jarNames数组做比较,如果文件名不同或新加或删除了 jar 文件都返回true

这里 jarNames 变量的初始赋值代码在 WebappClassLoader 类的 addJar 方法中的开头部分…

最后这一点点,看不下去了 (╯‵□′)╯︵┻━┻

结束

2019-05-16 小杭


参考资料

  • 源码分析三:web 应用加载原理

    • 《Tomcat 7 中 web 应用加载原理(一)Context 构建》
    • 《Tomcat 7 中 web 应用加载原理(二)web.xml 解析》
    • 《Tomcat 7 中 web 应用加载原理(三)Listener、Filter、Servlet 的加载和调用》
    • 《Tomcat 7 自动加载类及检测文件变动原理》

这篇关于Tomcat 源码分析(三)-(三)-自动加载类及检测文件变动原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/879510

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57