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


242

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

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

Answers:


151

每个类将使用其自己的类加载器来加载其他类。所以,如果ClassA.class引用ClassB.classClassB需要上的类加载器的类路径ClassA,或者它的父母。

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


1
您为什么说它ClassB必须在ClassA的loader(或ClassA的loader的父母)的类路径上?ClassA的加载程序是否有可能重写loadClass()ClassB即使ClassB它不在类路径中也可以成功加载?
Pacerier 2014年

8
实际上,并非所有类加载器都具有类路径。当写“ ClassB必须位于ClassA的类加载器的类路径上”时,我的意思是“ ClassB需要由ClassA的类加载器加载”。90%的时间他们表示相同。但是,如果您没有使用基于URL的类加载器,则只有第二种情况为真。
David Roussel 2014年

说“ ClassA.class引用ClassB.class” 是什么意思?
jameshfisher 2014年

1
当ClassA具有针对ClassB的import语句时,或者ClassA中存在一个具有ClassB类型的局部变量的方法时。如果尚未加载,则将触发ClassB加载。
David Roussel 2014年

我认为我的问题与这个话题有关。您如何看待我的解决方案?我知道,这不是一个很好的模式,但我没有任何其他的想法如何解决它:stackoverflow.com/questions/29238493/...
马辛ERBEL

109

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

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

当您要使用反射创建类时(例如反序列化或加载可配置的命名类时),执行反射的库应始终通过ClassLoader从应用程序接收作为参数来询问应用程序使用哪个类加载器。应用程序(知道所有需要构造的类)应该通过它getClass().getClassLoader()

任何其他获取类加载器的方法都不正确。如果库使用Thread.getContextClassLoader(),,之类的hack sun.misc.VM.latestUserDefinedLoader(),或者sun.reflect.Reflection.getCallerClass()是由于API不足引起的错误。基本上,Thread.getContextClassLoader()之所以存在,是因为设计该ObjectInputStreamAPI的人忘记了接受ClassLoaderas作为参数,而这一错误一直困扰着Java社区。

就是说,许多JDK类都使用一些技巧之一来猜测要使用的类加载器。有些使用ContextClassLoader(当您在共享线程池上运行其他应用程序时或退出时ContextClassLoader null失败),有些使用堆栈(当类的直接调用者本身是库时失败),有些使用系统类加载器(这很好,只要已记录它仅使用中的类CLASSPATH)或引导程序类加载器,而有些则使用上述技术的不可预测的组合(这只会使事情更加混乱)。这导致牙齿大哭大叫。

使用此类API时,首先,尝试查找将类加载器作为参数接受的方法的重载。如果没有明智的方法,请尝试ContextClassLoader在API调用之前设置(并在之后重置):

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);
}

5
是的,这就是我要问任何人的答案。
Marko Topolnik '16

6
这个答案的重点是使用类加载器加载类(通过反射或类似方式实例化它们),而它的另一个用途(事实上,我个人曾经使用过的唯一目的)是加载资源。是否应用相同的原理,或者在某些情况下您想通过上下文类加载器而不是调用者类加载器来获取资源?
Egor Hans

90

javaworld.com上有一篇文章解释了差异=> 您应该使用哪个ClassLoader

(1)

线程上下文类加载器为类加载委托方案提供了后门。

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

(2)来自同一来源:

Java可能会继续保持这种混乱状态。使用任何具有任何类型的动态资源加载的J2SE API,并尝试猜测它使用哪种加载策略。这是一个样本:

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

由于建议的解决方法是使核心JNDI类使用线程上下文加载器,因此我不明白这种情况有什么帮助。我们想使用父类加载器来加载实现供应商类,但它们对父类加载器不可见。因此,即使我们在线程的上下文类加载器中设置了该父类加载器,也如何使用父类加载它们。
2013年

6
@SAM,建议的解决方法实际上与您在最后所说的完全相反。不是将父bootstrap类加载器设置为上下文类加载器,而是将其设置为子system类路径类加载器ThreadJNDI然后,这些类将确保用于Thread.currentThread().getContextClassLoader()加载类路径上可用的JNDI实现类。
拉维·萨普利雅

“普通的J2SE委托不起作用”,我可以知道为什么它不起作用吗?因为Bootstrap ClassLoader只能从rt.jar加载类,而不能从应用程序的-classpath加载类?对?

37

除了@David Roussel答案,类可能会由多个类加载器加载。

让我们了解类加载器的工作原理。

来自java的javin paul博客:

在此处输入图片说明

在此处输入图片说明

ClassLoader 遵循三个原则。

委托原则

需要时,将类加载到Java中。假设您有一个名为Abc.class的应用程序特定的类,则加载该类的第一个请求将到达Application ClassLoader,它将委托给其父扩展ClassLoader,后者进一步委托给Primordial或Bootstrap类加载器

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

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

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

  • 应用程序类加载器Extension ClassLoader的子,其实现方式为sun.misc.Launcher$AppClassLoader类。

注意:Bootstrap类加载器(大多数以C语言以本机语言实现)之外,所有Java类加载器均使用来实现java.lang.ClassLoader

可见性原则

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

唯一性原则

根据此原则,父级加载的类不应再由子级ClassLoader加载

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.