【问题标题】:Are all dependencies of a class loaded by the same classloader?一个类的所有依赖项都是由同一个类加载器加载的吗?
【发布时间】:2012-12-26 13:57:15
【问题描述】:
  • 在我的/tomcat/lib 目录中,我有SharedClass 类(因此与所有网络应用程序共享)。
  • 在我的网络应用程序的WEB-INF/lib 中,我有课程LocalClass
  • SharedClass 引用了 LocalClass

在我的网络应用程序中,我尝试创建 SharedClass 的实例,但失败并显示以下消息:

NoClassDefFoundError: LocalClass.

由于SharedClass 是共享的,而LocalClass 是我的网络应用程序的本地,我希望它可以工作,但它没有。

我怀疑SharedClass 是由Tomcat 父类加载器加载的,LocalClass 是由Web App 类加载器加载的。由于SharedClass 是由父级加载的,我假设它的所有依赖项也必须由父级加载。因此,父级找不到LocalClass 并抛出错误。

这有意义吗?有没有办法解决这个问题(无需编写我自己的类加载器)?

【问题讨论】:

  • 在所有编辑之后,SharedClass 似乎直接引用了它LocalClass。在这种情况下,我展示的上下文类加载器技巧将不起作用,您需要将 JAR 打包在一起。
  • 所以如果我理解正确的话,一旦SharedClass 被父类加载器加载,它就被该类加载器“拥有”。因此,它引用的任何其他类,即使它们直到稍后才加载(例如,当调用方法时)仍将由 Parent 加载,而 Web App Classloader 将永远没有机会加载它们?如果我也使用反射来加载依赖类,这是真的吗?
  • 如果X.class.getClassLoader() == X.class.getClassLoader()X 只是X 的一个实例。父类加载器无法解析子类加载器中的类型,即使可以,它也只能看到其中一个。 感谢 Tomcat 不支持 parent-last 类加载器 - 那是一大堆错误。
  • @dough:没有。 SharedClass 由 Tomcat 类加载器加载,而 LocalClass 无法被该类加载器访问:它不在 Tomcat 类路径中。
  • @dough - 由于 JB Nizet 所说的原因,简单的反射不起作用。您可以使用上下文类加载器加载类,然后使用反射来调用它的方法。 但这是一个非常糟糕的主意

标签: java class tomcat web-applications classloader


【解决方案1】:

在普通 Java 应用程序中,当要求类加载器加载一个类时,它首先将请求委托给它的父类加载器,然后在父类加载器找不到请求的类时加载它。

对于 Web 应用程序服务器,这略有不同。对于部署在像 tomcat 这样的 Web 应用程序服务器中的每个 Web 应用程序,通常有不同的类加载器。对于 Tomcat,它如下所示 -

所以对于网络应用程序类加载资源按以下顺序发生 -

  1. JVM 的引导类(核心 java 类)
  2. /WEB-INF/Web 应用程序的类
  3. /WEB-INF/lib/*.jar 您的 Web 应用程序
  4. 系统类加载器类(Tomcat / Classpath 特定类)
  5. 通用类加载器类(所有网络应用通用的类)

但请注意,如果 Web 应用程序类加载器配置为 delegate="true",那么顺序会更改 -

  1. JVM 的引导类(核心 java 类)
  2. 系统类加载器类(Tomcat / Classpath 特定类)
  3. 通用类加载器类(所有网络应用通用的类)
  4. /WEB-INF/Web 应用程序的类
  5. /WEB-INF/lib/*.jar 您的 Web 应用程序

有关更多详细信息,您可以查看 Apache Tomcat 的 Class Loader HOW-TO 页面。

【讨论】:

    【解决方案2】:

    这是有道理的,它是 Tomcat 能够隔离应用程序的方式。更多信息可以在这里找到:http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

    共享类加载应用程序类或创建应用程序类实例的正当理由很少。对于需要这样做的极少数情况,您可以致电Thread.getCurrentThread().getContextClassloader()


    编辑:我觉得我正在将浸过 LOX 的煤球交给想要生火的人,所以以下是为什么 从共享类加载器加载应用程序类是个坏主意的原因 em>。

    直接的问题是您实际上并未将类加载到共享类加载器中,因此您无法轻松地操作您创建的对象。相反,您需要使用反射来调用方法,这会使您的代码变得一团糟。

    但更糟糕的问题是,如果您的 SharedClass 实例维护对 LocalClass 实例的引用,这些引用将阻止 Tomcat 取消部署应用程序。实际上,它会声称要取消部署,但在这些引用被收集之前,仍然会有旧应用程序的片段存在于 permgen 中,这通常会导致 permgen 耗尽。

    因此,虽然上下文类加载器是一个有用的工具——并且应该是您访问类路径资源的唯一方式——但它是一个很容易导致事情崩溃的工具。

    【讨论】:

    • 这种情况并不罕见。我有一个外部依赖项(ehcache)作为 tomcat 共享依赖项,但该依赖项依赖于其他依赖项(slf4j 等)。我唯一的选择是将传递依赖项也移动到共享目录。你有更好的建议吗?
    【解决方案3】:

    类加载器是分层的。类加载器有一个父级,并且可以看到其父级的类。反过来是不正确的。因此,由 webapp 的类加载器加载的类可以访问由通用 Tomcat 类加载器(其父类)加载的类,后者可以访问 JRE 类(Tomcat 类加载器的父类)。

    请参阅the Tomcat documentationClassLoader javadoc 了解更多详情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-20
      • 1970-01-01
      • 2019-06-05
      • 2012-01-20
      • 1970-01-01
      • 2014-08-29
      相关资源
      最近更新 更多