线程的上下文类装入器和普通类装入器之间的区别

线程的上下文类装入器和普通类装入器之间的区别是什么?

也就是说,如果Thread.currentThread().getContextClassLoader()getClass().getClassLoader()返回不同的类装入器对象,将使用哪一个?

126409 次浏览

每个类将使用自己的类加载器来加载其他类。因此,如果ClassA.class引用ClassB.class,则ClassB需要位于ClassA类加载器或其父类加载器的类路径上。

线程上下文类加载器是当前线程的当前类加载器。对象可以从ClassLoaderC中的类创建,然后传递给ClassLoaderD拥有的线程。在这种情况下,对象需要直接使用Thread.currentThread().getContextClassLoader(),如果它想加载在它自己的类加载器中不可用的资源。

infoworld.com上有一篇文章解释了这种区别 =比;您应该使用哪个ClassLoader < / p >

(1)

线程上下文类加载器提供了一个 后门周围的类加载 代表团计划。< / p > 以JNDI为例:它的胆量是 中的引导类实现 rt.jar(从J2SE 1.3开始),但是 这些核心JNDI类可以加载JNDI 由独立的提供者实现 供应商和潜在的部署 应用程序的-类路径。这 场景调用父节点 类加载器(在 在这种情况下)加载可见的类 它的子类加载器之一 例如,系统一)。正常的J2SE 委托不起作用,而且 解决方法是使核心JNDI 类使用线程上下文加载器, 因此有效地“挖掘”;通过 类中的类装入器层次结构 与固有方向相反的方向 代表团。< / p >

(2)来源相同:

这种困惑可能会持续下去 Java的一段时间。以任何J2SE API为例 与动态资源加载任何 试着猜一下是哪个装载 它使用的策略。下面是一个例子:

  • JNDI使用上下文类加载器
  • Class.getResource()和Class.forName()使用当前的类加载器
  • JAXP使用上下文类加载器(从J2SE 1.4开始)
  • resourcebundle使用调用者当前的类加载器
  • 通过java.protocol.handler.pkgs系统属性指定的URL协议处理程序只在引导和系统类加载器中查找
  • Java Serialization API默认使用调用者当前的类加载器

补充@David Roussel的回答,类可以由多个类装入器装入。

让我们了解类装入器是如何工作的。

来自javin paul博客中的javarevisited:

enter image description here

enter image description here

ClassLoader遵循三个原则。

代表团的原则

当需要时,在Java中装入类。假设你有一个应用程序特定的类叫做Abc.class,加载这个类的第一个请求将来到应用程序类加载器,它将委托给它的父类扩展类加载器,扩展类加载器进一步委托给原始类或引导类加载器

  • 负责从rt.jar加载标准JDK类文件,它是Java中所有类加载器的父类。引导类装入器没有任何父类。

  • 扩展类加载器将类加载请求委托给它的父类Bootstrap,如果不成功,则从jre/lib/ext目录或java.ext.dirs系统属性所指向的任何其他目录中加载类

  • 系统或应用程序类加载器,它负责从CLASSPATH环境变量,-classpath或-cp命令行选项,JAR内Manifest文件的类路径属性加载应用程序特定的类。

  • 应用程序类加载器扩展类加载器的子类,它由sun.misc.Launcher$AppClassLoader类实现。

除了引导类装入器主要用C语言在本地语言中实现之外,所有Java类加载器都使用java.lang.ClassLoader实现。

可见性原则

根据可见性原则,儿童类加载器可以看到由父类加载器加载的类,反之则不然。

唯一性原则

根据这个原则,父类加载器加载的类不应该再被子类加载器加载

这并没有回答最初的问题,但由于这个问题对于任何ContextClassLoader查询都是高度排序和链接的,我认为回答何时应该使用上下文类装入器的相关问题是很重要的。简单的回答:永远不要使用上下文类装入器!但是当你必须调用一个缺少ClassLoader参数的方法时,将它设置为getClass().getClassLoader()

当一个类的代码请求加载另一个类时,要使用的正确类装入器是与调用方类相同的类装入器(即getClass().getClassLoader())。这是事情99.9%的工作方式,因为这就是JVM本身所做的在你第一次构造一个新类的实例,调用一个静态方法,或访问一个静态字段。

当你想要使用反射创建一个类(比如反序列化或加载一个可配置的命名类)时,进行反射的库应该通过从应用程序接收ClassLoader作为参数来总是询问应用程序要使用的类装入器。应用程序(它知道所有需要构造的类)应该将getClass().getClassLoader()传递给它。

任何其他获取类装入器的方法都是不正确的。如果库使用诸如Thread.getContextClassLoader()sun.misc.VM.latestUserDefinedLoader()sun.reflect.Reflection.getCallerClass()之类的hack,则是由API中的缺陷引起的错误。基本上,Thread.getContextClassLoader()的存在只是因为设计ObjectInputStream API的人忘记接受ClassLoader作为参数,这个错误一直困扰着Java社区直到今天。

也就是说,许多JDK类使用一些技巧来猜测要使用的类装入器。一些使用ContextClassLoader(失败当您运行不同的应用程序在一个共享的线程池,或当你离开ContextClassLoader null),一些走堆栈(失败时直接调用者的类本身就是一个库),一些使用系统类加载器(这是很好,只要是记录只使用类CLASSPATH)或引导类装入器,和一些使用上面的不可预知的组合技术(这只会让事情更加混乱)。这导致了许多人的哭泣和咬牙切齿。

当使用这样的API时,首先,尝试找到接受类装入器作为参数的方法的重载.;如果没有合理的方法,则尝试在API调用之前设置ContextClassLoader(并在调用之后重新设置它):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
// call some API that uses reflection without taking ClassLoader param
} finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}