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

abr*_*bra 225 java multithreading jvm classloader

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

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

Dav*_*sel 141

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

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

  • 实际上,并非所有类加载器都具有类路径.当写"ClassB需要在ClassA的类加载器的类路径上"时,我的意思是"ClassB需要由ClassA的类加载器加载".90%的时间他们的意思相同.但是如果你没有使用基于URL的类加载器,那么只有第二种情况才是真的. (6认同)
  • 当ClassA有ClassB的导入语句时,或者ClassA中有一个方法有ClassB类型的局部变量时。如果 ClassB 尚未加载,这将触发 ClassB 的加载。 (2认同)

yon*_*ran 93

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

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

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

获取类加载器的任何其他方法都是错误的.如果库使用黑客等Thread.getContextClassLoader(),sun.misc.VM.latestUserDefinedLoader()或者sun.reflect.Reflection.getCallerClass()它是由在API中缺乏的错误.基本上,Thread.getContextClassLoader()存在只是因为设计ObjectInputStreamAPI的人忘记接受ClassLoader作为参数,而这个错误一直困扰着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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案的重点是使用类加载器来加载类(通过反射或类似方式实例化它们),而它的另一个用途(事实上,我个人使用它的唯一目的)是加载资源。同样的原则是否适用,或者是否存在您希望通过上下文类加载器而不是调用者类加载器获取资源的情况? (12认同)
  • 是的,这是我向任何提出问题的人指出的答案. (3认同)
  • 请注意,“getClass().getClassLoader()”不一定与“ThisClass.class.getClassLoader()”相同,除非“ThisClass”是“final”或者您知道子类不存在。 (3认同)

Tim*_*our 86

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

(1)

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

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

(2)来自同一来源:

这种困惑可能会持续Java一段时间.使用任何类型的动态资源加载任何J2SE API,并尝试猜测它使用的加载策略.这是一个抽样:

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

  • @SAM,建议的解决方法实际上与你最后说的完全相反.它不是设置为上下文类加载器的父`bootstrap`类加载器,而是设置`Thread`的子`system`类路径类加载器.然后,`JNDI`类确保使用`Thread.currentThread().getContextClassLoader()`来加载类路径上可用的JNDI实现类. (6认同)

Rav*_*abu 32

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

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

来自javarevisited的javin paul博客:

在此输入图像描述

在此输入图像描述

ClassLoader 遵循三个原则.

授权原则

在需要时,用Java加载类.假设您有一个名为Abc.class的特定于应用程序的类,首先加载此类的请求将来到Application ClassLoader,它将委托给它的父ExtensionLoader,它进一步委托给Primordial或Bootstrap类加载器.

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

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

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

  • Application类加载器Extension ClassLoader的子,它由sun.misc.Launcher$AppClassLoader类实现.

注意:除了Bootstrap类加载器(主要在C语言中使用本机语言实现)之外,所有Java类加载器都是使用java.lang.ClassLoader.

可见性原则

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

唯一性原则

根据这个原则,父类加载的类不应再次由Child ClassLoader加载