• 售前

  • 售后

热门帖子
入门百科

Tomcat 类加载器的实现方法及实例代码

[复制链接]
下一站美安好 显示全部楼层 发表于 2021-10-26 12:48:51 |阅读模式 打印 上一主题 下一主题
Tomcat 内部界说了多个 ClassLoader,以便应用和容器访问差别存储库中的类和资源,同时达到应用间类隔离的目的。
1. Java 类加载机制
类加载就是把编译天生的 class 文件,加载到 JVM 内存中(永世代/元空间)。
类加载器之所以能实现类隔离,是由于两个类相称的条件是它们由同一个类加载器加载,否则必定不相称。
JVM 在加载时,接纳的是一种双亲委托机制,当类加载器要加载一个类时,加载顺序是:
首先将哀求委托给父加载器,假如父加载器找不到要加载的类然后再查找自己的存储库实验加载
这个机制的利益就是可以大概包管核心类库不被覆盖。
而按照 Servlet 规范的建议,Webapp 加载器略有差别,它首先会在自己的资源库中搜刮,而不是向上委托,冲破了尺度的委托机制,来看下 Tomcat 的筹划和实现。
2. Tomcat 类加载器筹划
Tomcat 团体类加载器结构如下:

此中 JDK 内部提供的类加载器分别是:
Bootstrap - 启动类加载器,属于 JVM 的一部门,加载 <JAVA_HOME>/lib/ 目次下特定的文件Extension - 扩展类加载器,加载 <JAVA_HOME>/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 初始化的,源码如下:
  1. private void initClassLoaders() {
  2. try {
  3. commonLoader = createClassLoader("common", null);
  4. if( commonLoader == null ) {
  5.   // no config file, default to this loader - we might be in a 'single' env.
  6.   commonLoader=this.getClass().getClassLoader();
  7. }
  8. // 指定仓库路径配置文件前缀和父加载器,创建 ClassLoader 实例
  9. catalinaLoader = createClassLoader("server", commonLoader);
  10. sharedLoader = createClassLoader("shared", commonLoader);
  11. } catch (Throwable t) {
  12. log.error("Class loader creation threw exception", t);
  13. System.exit(1);
  14. }
  15. }
复制代码
可以看到分别创建了三个类加载器,createClassLoader 就是根据设置获取资源仓库所在,末了返回一个 StandardClassLoader 实例,核心代码如下:
  1. private ClassLoader createClassLoader(String name, ClassLoader parent)
  2. throws Exception {
  3. String value = CatalinaProperties.getProperty(name + ".loader");
  4. if ((value == null) || (value.equals("")))
  5.   return parent; // 如果没有配置,则返回传入的父加载器
  6. ArrayList repositoryLocations = new ArrayList();
  7. ArrayList repositoryTypes = new ArrayList();
  8. ...
  9. // 获取资源仓库路径
  10. String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
  11. Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
  12. // 创建一个 StandardClassLoader 对象
  13. ClassLoader classLoader = ClassLoaderFactory.createClassLoader
  14.    (locations, types, parent);
  15. ...
  16. return classLoader;
  17. }
复制代码
类加载器初始化完毕后,会创建一个 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
  1. public synchronized Class loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException {
  3. ...
  4. Class clazz = null;
  5. // (0) 检查自身内部缓存中是否已经加载
  6. clazz = findLoadedClass0(name);
  7. if (clazz != null) {
  8. if (log.isDebugEnabled())
  9.   log.debug(" Returning class from cache");
  10. if (resolve) resolveClass(clazz);
  11. return (clazz);
  12. }
  13. // (0.1) 检查 JVM 的缓存中是否已经加载
  14. clazz = findLoadedClass(name);
  15. if (clazz != null) {
  16. if (log.isDebugEnabled())
  17.   log.debug(" Returning class from cache");
  18. if (resolve) resolveClass(clazz);
  19. return (clazz);
  20. }
  21. // (0.2) 尝试使用系统类加载加载,防止覆盖 J2SE 类
  22. try {
  23. clazz = system.loadClass(name);
  24. if (clazz != null) {
  25.   if (resolve) resolveClass(clazz);
  26.   return (clazz);
  27. }
  28. } catch (ClassNotFoundException e) {// Ignore}
  29. // (0.5) 使用 SecurityManager 检查是否有此类的访问权限
  30. if (securityManager != null) {
  31. int i = name.lastIndexOf('.');
  32. if (i >= 0) {
  33.   try {
  34.   securityManager.checkPackageAccess(name.substring(0,i));
  35.   } catch (SecurityException se) {
  36.   String error = "Security Violation, attempt to use " +
  37.    "Restricted Class: " + name;
  38.   log.info(error, se);
  39.   throw new ClassNotFoundException(error, se);
  40.   }
  41. }
  42. }
  43. boolean delegateLoad = delegate || filter(name);
  44. // (1) 是否委托给父类,这里默认为 false
  45. if (delegateLoad) {
  46.   ...
  47. }
  48. // (2) 尝试查找自己的存储库并加载
  49. try {
  50. clazz = findClass(name);
  51. if (clazz != null) {
  52.   if (log.isDebugEnabled())
  53.   log.debug(" Loading class from local repository");
  54.   if (resolve) resolveClass(clazz);
  55.   return (clazz);
  56. }
  57. } catch (ClassNotFoundException e) {}
  58. // (3) 如果此时还加载失败,那么将加载请求委托给父加载器
  59. if (!delegateLoad) {
  60. if (log.isDebugEnabled())
  61.   log.debug(" Delegating to parent classloader at end: " + parent);
  62. ClassLoader loader = parent;
  63. if (loader == null)
  64.   loader = system;
  65. try {
  66.   clazz = loader.loadClass(name);
  67.   if (clazz != null) {
  68.   if (log.isDebugEnabled())
  69.    log.debug(" Loading class from parent");
  70.   if (resolve) resolveClass(clazz);
  71.   return (clazz);
  72.   }
  73. } catch (ClassNotFoundException e) {}
  74. }
  75. // 最后加载失败,抛出异常
  76. throw new ClassNotFoundException(name);
  77. }
  78. 在防止覆盖 J2SE 类的时候,版本 Tomcat 6,使用的是 AppClassLoader,rt.jar 核心类库是由 Bootstrap Classloader 加载的,但是在 Java 代码是获取不了这个加载器的,在高版本做了以下优化:
  79. ClassLoader j = String.class.getClassLoader();
  80. if (j == null) {
  81. j = getSystemClassLoader();
  82. while (j.getParent() != null) {
  83. j = j.getParent();
  84. }
  85. }
  86. this.javaseClassLoader = j;
复制代码
类的时候,版本 Tomcat 6,使用的是 AppClassLoader,rt.jar 核心类库是由 Bootstrap Classloader 加载的,但是在 Java 代码是获取不了这个加载器的,在高版本做了以下优化:
  1. ClassLoader j = String.class.getClassLoader();
  2. if (j == null) {
  3. j = getSystemClassLoader();
  4. while (j.getParent() != null) {
  5. j = j.getParent();
  6. }
  7. }
  8. this.javaseClassLoader = j;
复制代码
也就是使用尽可能接近 Bootstrap 加载器的类加载器。
6. 小结
相信大部门人都遇到过 ClassNotFoundException 这个非常,这背后就涉及到了类加载器,对加载的原理有一定的相识,有助于排查标题。
以上所述是小编给各人介绍的Tomcat 类加载器的实现方法及实例代码,盼望对各人有所资助,假如各人有任何疑问请给我留言,小编会及时回复各人的。在此也非常感谢各人对脚本之家网站的支持!
假如你觉得本文对你有资助,欢迎转载,烦请注明出处,谢谢!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作