线程的上下文类加载器和普通的类加载器之间的区别

线程的上下文类加载器和普通的类加载器有什么区别?

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

每个类将使用自己的类加载器来加载其他类。 所以,如果ClassA.class引用ClassB.classClassB需要位于ClassA的类加载器或其父代的类path上。

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

有一篇关于javaworld.com的文章解释了差异=> 你应该使用哪一个ClassLoader

(1)

线程上下文类加载器提供了类加载委托scheme的后门。

以JNDI为例:它的胆量由rt.jar中的引导类实现(从J2SE 1.3开始),但是这些核心的JNDI类可能会加载由独立供应商实现的JNDI提供者,并可能部署在应用程序的-classpath中。 这个场景需要一个父类加载器(在这种情况下是原始类加载器)来加载其子类加载器(例如系统之一)可见的一个类。 正常的J2SE委派不起作用,解决方法是使核心JNDI类使用线程上下文加载器,从而有效地通过类加载器层次结构向合适委派相反的方向“隧穿”。

(2)来自同一来源:

这种困惑可能会持续一段时间。 采取任何types的dynamic资源加载任何J2SE API,并尝试猜测它使用哪种加载策略。 这是一个抽样:

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

这不回答原来的问题,但由于任何ContextClassLoader查询的问题是高度排名和链接的,我认为重要的是要回答相关的问题,应该使用上下文类加载器。 简短的回答: 永远不要使用上下文类加载器 ! 但是当你必须调用缺lessClassLoader参数的方法时,将其设置为getClass().getClassLoader()

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

当你想使用reflection来创build一个类时(比如在反序列化或加载一个可configuration的命名类时),通过从应用程序接收ClassLoader作为参数,reflection的库应该总是询问应用程序使用哪个类加载器。 应用程序(知道所有需要构造的类)应该通过它的getClass().getClassLoader()

任何其他获得类加载器的方式都是不正确的。 如果一个库使用诸如Thread.getContextClassLoader()sun.misc.VM.latestUserDefinedLoader()Thread.getContextClassLoader()这样的黑客,那么这是一个缺陷导致的错误。 基本上, Thread.getContextClassLoader()存在只是因为deviseObjectInputStream API的人忘记接受ClassLoader作为参数,并且这个错误一直困扰着Java社区。

也就是说,许多JDK类使用几个黑客之一来猜测一些类加载器使用。 一些使用ContextClassLoader (当你在一个共享线程池上运行不同的应用程序,或者当你离开ContextClassLoader null时候会失败),一些使用ContextClassLoader null (当类的直接调用者本身就是一个库的时候会失败),一些使用系统类加载器(这很好,只要它被logging为仅使用CLASSPATH类)或引导类加载器,并且一些使用上述技术的不可预知的组合(这只会使事情更加混乱)。 这导致了许多哭泣和咬牙切齿。

当使用这样的API时, 首先尝试find接受类加载器的方法的一个重载作为参数 。 如果没有明智的方法,那么在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); } 

添加到@David Roussel答案,类可以由多个类加载器加载。

让我们了解类加载器如何工作。

来自javin paul博客在javarevisited:

在这里输入图像描述

在这里输入图像描述

ClassLoader遵循三个原则。

代表团原则

一个类在需要的时候被加载到Java中。 假设你有一个名为Abc.class的应用程序特定的类,加载这个类的第一个请求将来到Application ClassLoader,它将委托给它的父扩展类加载器,它进一步委托给Primordial或Bootstrap类加载器

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

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

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

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

注意:Bootstrap类加载器 (以C语言为本机语言实现)外,所有Java类加载器都使用java.lang.ClassLoader实现。

可见性原则

根据可见性原则, Child ClassLoader可以看到由类加载加载的 ,反之亦然。

唯一性原则

根据这个原则,一个由Parent加载的类不应该被Child ClassLoader再次加载