千家信息网

使用Tomcat怎么实现类加载器

发表于:2025-12-02 作者:千家信息网编辑
千家信息网最后更新 2025年12月02日,使用Tomcat怎么实现类加载器?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1. Java 类加载机制类加载就是把编译
千家信息网最后更新 2025年12月02日使用Tomcat怎么实现类加载器

使用Tomcat怎么实现类加载器?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

1. Java 类加载机制

类加载就是把编译生成的 class 文件,加载到 JVM 内存中(永久代/元空间)。

类加载器之所以能实现类隔离,是因为两个类相等的前提是它们由同一个类加载器加载,否则必定不相等。

JVM 在加载时,采用的是一种双亲委托机制,当类加载器要加载一个类时,加载顺序是:

首先将请求委托给父加载器,如果父加载器找不到要加载的类然后再查找自己的存储库尝试加载

这个机制的好处就是能够保证核心类库不被覆盖。

而按照 Servlet 规范的建议,Webapp 加载器略有不同,它首先会在自己的资源库中搜索,而不是向上委托,打破了标准的委托机制,来看下 Tomcat 的设计和实现。

2. Tomcat 类加载器设计

Tomcat 整体类加载器结构如下:

其中 JDK 内部提供的类加载器分别是:

Bootstrap - 启动类加载器,属于 JVM 的一部分,加载 /lib/ 目录下特定的文件Extension - 扩展类加载器,加载 /lib/ext/ 目录下的类库Application - 应用程序类加载器,也叫系统类加载器,加载 CLASSPATH 指定的类库

Tomcat 自定义实现的类加载器分别是:

Common - 父加载器是 AppClassLoader,默认加载 ${catalina.home}/lib/ 目录下的类库Catalina - 父加载器是 Common 类加载器,加载 catalina.properties 配置文件中 server.loader 配置的资源,一般是 Tomcat 内部使用的资源Shared - 父加载器是 Common 类加载器,加载 catalina.properties 配置文件中 shared.loader 配置的资源,一般是所有 Web 应用共享的资源WebappX - 父加载器是 Shared 加载器,加载 /WEB-INF/classes 的 class 和 /WEB-INF/lib/ 中的 jar 包JasperLoader - 父加载器是 Webapp 加载器,加载 work 目录应用编译 JSP 生成的 class 文件

在实现时,上图不是继承关系,而是通过组合体现父子关系。Tomcat 类加载器的源码类图:

Common、Catalina 、Shared 它们都是 StandardClassLoader 的实例,在默认情况下,它们引用的是同一个对象。其中 StandardClassLoader 与 URLClassLoader 没有区别;WebappClassLoader 则按规范实现以下顺序的查找并加载:

从 JVM 内部的 Bootstrap 仓库加载从应用程序加载器路径,即 CLASSPATH 下加载从 Web 程序内的 /WEB-INF/classes 目录从 Web 程序内的 /WEB-INF/lib 中的 jar 文件从容器 Common 加载器仓库,即所有 Web 程序共享的资源加载

接下来看下源码实现。

3. 自定义加载器的初始化

common 类加载器是在 Bootstrap 的 initClassLoaders 初始化的,源码如下:

private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) {  // no config file, default to this loader - we might be in a 'single' env.  commonLoader=this.getClass().getClassLoader(); } // 指定仓库路径配置文件前缀和父加载器,创建 ClassLoader 实例 catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { log.error("Class loader creation threw exception", t); System.exit(1); }}

可以看到分别创建了三个类加载器,createClassLoader 就是根据配置获取资源仓库地址,最后返回一个 StandardClassLoader 实例,核心代码如下:

private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals("")))  return parent; // 如果没有配置,则返回传入的父加载器 ArrayList repositoryLocations = new ArrayList(); ArrayList repositoryTypes = new ArrayList(); ... // 获取资源仓库路径 String[] locations = (String[]) repositoryLocations.toArray(new String[0]); Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]); // 创建一个 StandardClassLoader 对象 ClassLoader classLoader = ClassLoaderFactory.createClassLoader   (locations, types, parent); ... return classLoader;}

类加载器初始化完毕后,会创建一个 Catalina 对象,最终会调用它的 load 方法,解析 server.xml 初始化容器内部组件。那么容器,比如 Engine,又是怎么关联到这个设置的父加载器的呢?

Catalina 对象有一个 parentClassLoader 成员变量,它是所有组件的父加载器,默认是 AppClassLoader,在此对象创建完毕时,会反射调用它的 setParentClassLoader 方法,将父加载器设为 sharedLoader。

而 Tomcat 内部顶级容器 Engine 在初始化时,Digester 有一个 SetParentClassLoaderRule 规则,会将 Catalina 的 parentClassLoader 通过 Engine.setParentClassLoader 方法关联起来。

4. 如何打破双亲委托机制

答案是使用 Thread.getContextClassLoader() - 当前线程的上下文加载器,该加载器可通过 Thread.setContextClassLoader() 在代码运行时动态设置。

默认情况下,Thread 上下文加载器继承自父线程,也就是说所有线程默认上下文加载器都与第一个启动的线程相同,也就是 main 线程,它的上下文加载器是 AppClassLoader。

Tomcat 就是在 StandardContext 启动时首先初始化一个 WebappClassLoader 然后设置为当前线程的上下文加载器,最后将其封装为 Loader 对象,借助容器之间的父子关系,在加载 Servlet 类时使用。

5. Web 应用的类加载

Web 应用的类加载是由 WebappClassLoader 的方法 loadClass(String, boolean) 完成,核心代码如下:

在防止覆盖 J2SE

public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { ... Class clazz = null; // (0) 检查自身内部缓存中是否已经加载 clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled())  log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) 检查 JVM 的缓存中是否已经加载 clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled())  log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) 尝试使用系统类加载加载,防止覆盖 J2SE 类 try { clazz = system.loadClass(name); if (clazz != null) {  if (resolve) resolveClass(clazz);  return (clazz); } } catch (ClassNotFoundException e) {// Ignore} // (0.5) 使用 SecurityManager 检查是否有此类的访问权限 if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) {  try {  securityManager.checkPackageAccess(name.substring(0,i));  } catch (SecurityException se) {  String error = "Security Violation, attempt to use " +   "Restricted Class: " + name;  log.info(error, se);  throw new ClassNotFoundException(error, se);  } } } boolean delegateLoad = delegate || filter(name); // (1) 是否委托给父类,这里默认为 false if (delegateLoad) {  ... } // (2) 尝试查找自己的存储库并加载 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) {} // (3) 如果此时还加载失败,那么将加载请求委托给父加载器 if (!delegateLoad) { if (log.isDebugEnabled())  log.debug(" Delegating to parent classloader at end: " + parent); ClassLoader loader = parent; if (loader == null)  loader = system; try {  clazz = loader.loadClass(name);  if (clazz != null) {  if (log.isDebugEnabled())   log.debug(" Loading class from parent");  if (resolve) resolveClass(clazz);  return (clazz);  } } catch (ClassNotFoundException e) {} } // 最后加载失败,抛出异常 throw new ClassNotFoundException(name);}在防止覆盖 J2SE 类的时候,版本 Tomcat 6,使用的是 AppClassLoader,rt.jar 核心类库是由 Bootstrap Classloader 加载的,但是在 Java 代码是获取不了这个加载器的,在高版本做了以下优化:ClassLoader j = String.class.getClassLoader();if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) { j = j.getParent(); }}this.javaseClassLoader = j;

类的时候,版本 Tomcat 6,使用的是 AppClassLoader,rt.jar 核心类库是由 Bootstrap Classloader 加载的,但是在 Java 代码是获取不了这个加载器的,在高版本做了以下优化:

ClassLoader j = String.class.getClassLoader();if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) { j = j.getParent(); }}this.javaseClassLoader = j;

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

资源 文件 委托 配置 对象 线程 应用 上下 上下文 仓库 代码 容器 机制 核心 目录 程序 就是 方法 版本 实例 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 沭阳巨型网络技术诚信服务 拳王的金腰带网络技术 超好看手抄报一等奖网络安全 软件开发团队人才招募 浙江软件开发五星服务 西城区专业软件开发服务保障 道德意识是否与软件开发人员相关 宿豫区网络技术有限公司 甲基化位点与基因表达数据库 网络安全是一门涉及哪些学科 三维数据库使用教程 什么服务器最适合做app RECOLL检索软件开发 网络安全管理师成绩查询 空军的计算机网络技术 软件开发需求管理工具 数据库表如何设置主外键 ssd云服务器评测 数据库安全性分哪两类 荒野行动进游戏服务器连接半天 流控服务器器 育碧的土豆服务器 网络安全战队是什么意思 软件工程金融软件开发是什么 注册网络安全行业协会流程 网络安全状况及解决方法 华为网络技术学院 校企合作 爬他人数据库违法吗 网络安全伴我行演讲稿 川大首招网络安全人才
0