当前位置: 首页 > news >正文

、Tomcat源码分析-类加载器

接下来,我们再来看下 tomcat 是如何创建 common 类加载器的。关键代码如下所示,在创建类加载器时,会读取相关的路径配置,并把路径封装成 Repository 对象,然后交给 ClassLoaderFactory 创建类加载器。

Bootstrap.java
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {// 从catalina.propeties中读取配置,并替换 catalina.home、或者catalina.base,或者环境变量String value = CatalinaProperties.getProperty(name + ".loader");value = replace(value);// 遍历目录,并对路径进行处理List<Repository> repositories = new ArrayList<>();String[] repositoryPaths = getPaths(value);for (String repository : repositoryPaths) {//TODO 将路径封装成 Repository 对象}return ClassLoaderFactory.createClassLoader(repositories, parent);

我们再进一步对 ClassLoaderFactory 进行分析,都是细节上的处理,比如利用文件路径构造带有明显协议的 URL 对象,例如本地文件的标准 URL 是 file:/D:/app.jar。另外,在创建 URLClassLoader 的时候还需要考虑 jdk 对权限控制的影响,因此 tomcat 利用 AccessController 创建 URLClassLoader,由此可见 tomcat 编码的严谨性。而我们在实际的开发过程中,有时候需要自定义类加载器,但往往不会考虑权限控制这块,所以在对类加载器进行编码时需要注意一下

ClassLoaderFactory.java
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent)throws Exception {Set<URL> set = new LinkedHashSet<>();if (repositories != null) {for (Repository repository : repositories)  {// 对不同类型的 Repository 对象进行处理,将路径转换为URL类型// 因为 URL 类型带有明显的协议,比如jar:xxx、file:xxx}}// 将对应的路径组装成 URLfinal URL[] array = set.toArray(new URL[set.size()]);// 在创建 URLClassLoader 需要考虑到 AccessController 的影响return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {public URLClassLoader run() {if (parent == null) return new URLClassLoader(array);else return new URLClassLoader(array, parent);}});
}private static URL buildClassLoaderUrl(File file) throws MalformedURLException {String fileUrlString = file.toURI().toString();fileUrlString = fileUrlString.replaceAll("!/", "%21/"); // 转换成URL编码return new URL(fileUrlString);

OK,前面介绍了 tomcat 创建类加载器的过程,接下来我们看下 tomcat 类加载器的具体应用场景

WebappClassLoader

在前面,我们介绍了 tomcat 类加载器的设计,每个 webapp 使用单独的类加载器完成我们开发的 webapp 应用程序的类加载,而每一个 webapp 对应一个 WebappClassLoader。tomcat7 默认使用 WebappClassLoader 类加载器,而 tomcat8 默认使用 ParallelWebappClassLoader,支持并行加载类的特性,这也算是 tomcat8 做的一些优化吧,而实际上也是利用 jdk 的功能,需要同时满足以下两点才支持并行加载类,并且一旦注册了并行加载的能力,就不能回退了

1、 没有创建调用者的实例
2、 调用者的所有超类(除了类对象)都是并行注册的

基于上面两点,因此,ParallelWebappClassLoader 在 static 代码块中注册并行加载机制,而它的父类 URLClassLoader 父类也是具有并行能力的,关键代码如下所示:

public class ParallelWebappClassLoader extends WebappClassLoaderBase {static {boolean result = ClassLoader.registerAsParallelCapable();if (!result) {log.warn(sm.getString("webappClassLoaderParallel.registrationFailed"));}}// 省略无关代码...

WebappClassLoader 的类图如下所示,其中 WebappClassLoaderBase 实现了主要的逻辑,并且继承了 Lifecycle,在 tomcat 组件启动、关闭时会完成资源的加载、卸载操作,例如在 start 过程会读取我们熟悉的 /WEB-INF/classes/WEB-INF/lib 资源,并且记录每个 jar 包的时间戳方便重载 jar 包;而在组件 stop 的时候,会清理已经加载的资源;destory 时会显式地触发 URLClassLoader.close()。这个 Lifecycle 真是无处不在啊

单独的类加载器是无法获取 webapp 的资源信息的,因此 tomcat 引入了 WebappLoader,便于访问 Context 组件的信息,同时为 Context 提供类加载的能力支持,下面我们分析下 WebappLoader 的底层实现

WebappLoader

我们先来看看 WebappLoader 几个重要的属性,内部持有 Context 组件,并且有个我们熟悉的 reloadable 参数,如果设为 true,则会开启类的热加载机制

public class WebappLoader extends LifecycleMBeanBaseimplements Loader, PropertyChangeListener {private WebappClassLoaderBase classLoader = null;   // 默认使用ParallelWebappClassLoaderprivate Context context = null;private String loaderClass = ParallelWebappClassLoader.class.getName();private ClassLoader parentClassLoader = null;       // 父加载器,默认为 catalina 类加载器private boolean reloadable = false;                 // 是否支持热加载类private String classpath = null;

在 tomcat 中,每个 webapp 对应一个 StandardContext,在 start 过程便会实例化 WebappLoader,并且调用其 start 方法完成初始化,包括创建 ParallelWebappClassLoader 实例,然后,还会启动 Context 的子容器。注意,这两个过程,都会将线程上下文类加载器指定为 ParallelWebappClassLoader 类加载器,在完成 webapp 相关的类加载之后,又将线程上下文类加载器设置为 catalina 类加载器。Context 容器的启动过程,这里便不再重复了,感兴趣的童鞋请查看前面的博文 webapp源码分析

StandardContext.javaprotected synchronized void startInternal() throws LifecycleException {// 实例化 Loader 实例,它是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);    // 使用了读写锁控制并发问题}// 将 Loader 中的 ParallelWebappClassLoader 绑定到当前线程中,并返回 catalian 类加载器ClassLoader oldCCL = bindThread();try {if (ok) {// 如果 Loader 是 Lifecycle 实现类,则启动该 LoaderLoader loader = getLoader();if (loader instanceof Lifecycle) {((Lifecycle) loader).start();}// 设置 ClassLoader 的各种属性setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets());// 省略……// 解除线程上下文类加载器绑定unbindThread(oldCCL);oldCCL = bindThread();// 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是加载 Context 的子容器fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);// 启动子容器for (Container child : findChildren()) {if (!child.getState().isAvailable()) {child.start();}}}} finally {// Unbinding threadunbindThread(oldCCL);}
http://www.lryc.cn/news/35933.html

相关文章:

  • 反转链表相关的练习(下)
  • 2.进程和线程
  • C++回顾(十四)—— 函数模板
  • 如何做好项目各干系人的管理及应对?
  • Elasticsearch使用系列-ES增删查改基本操作+ik分词
  • 07-PL/SQL基础(if语句,case语句,循环语句)
  • 信捷 XDH Ethercat A_VELMOVE
  • 【专项训练】分治、回溯
  • Linux上安装配置ZooKeeper
  • idea leetcode插件无法登录
  • VR会议不断升级,为商务会谈打造云端洽谈服务!
  • Ubuntu系统开机自动挂载NTFS硬盘【超实用】
  • 淘宝十年资深架构师吐血总结淘宝的数据库架构设计和采用的技术手段。
  • 训练自己的GPT2-Chinese模型
  • springcloud3 fegin服务超时的配置和日志级别的配置2
  • 华为机试 HJ48 从单向链表中删除指定值的节点
  • 华为机试 HJ1 字符串最后一个单词的长度
  • 从入门到精通MongoDB数据库系列之二:深入了解MongoDB基本概念文档、集合、数据库、数据类型、MongoDB shell
  • 前端实用技巧,JS压缩、美化、JS混淆加密
  • synchronized轻量级锁优化
  • python 日期转换 日期字符相关
  • 使用sm4js进行加密和国密sm4的总结
  • c语言面试题目整理
  • 【嵌入式Bluetooth应用开发笔记】第三篇:初探BLUE ALSA应用开发
  • C++基础了解-21-C++ 继承
  • To_Heart—游记——NOI 春季测试
  • Laya小游戏开发,laya3D美术篇——1——关于laya自带的几个shader的基础运用讲解。
  • brew 的安装与使用 (Apple Silicon CPU的Mac机型完成适配)
  • 传输线的物理基础(四):传输线的特性阻抗
  • (六十九)以MySQL单表查询来举例,看看执行计划包含哪些内容(2)?