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

问题

线程的上下文类加载器和普通的类加载器有什么区别?
也就是说,ifThread.currentThread().getContextClassLoader()getClass().getClassLoader()返回不同的类加载器对象,将使用哪一个?


#1 热门回答(121 赞)

每个类都将使用自己的类加载器来加载其他类。因此ifClassA.classreferencesClassB.classthenClassB需要在类加载器的类路径ClassA或其父级。

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


#2 热门回答(82 赞)

javaworld.com上有一篇文章解释了差异=> Which ClassLoader should you use

(1)

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

(2)来自同一来源:

这种困惑可能会持续一段时间。使用任何类型的动态资源加载任何J2SE API,并尝试猜测它使用的加载策略。下面是一个示例:JNDI使用上下文类加载器Class.getResource()和Class.forName()使用当前的类加载器JAXP使用上下文类加载器(截至J2SE 1.4)java.util.ResourceBundle使用调用者通过java指定的当前类加载器URL协议处理程序.protocol.handler.pkgs系统属性在引导程序和系统类加载器中查找只有Java Serialization API默认使用调用程序的当前类加载器


#3 热门回答(64 赞)

这并没有回答原始问题,但由于问题是高度排名和链接的anyContextClassLoaderquery,我认为回答关于何时应该使用上下文类加载器的相关问题很重要。简答:从不使用上下文类加载器!但是当你必须调用一个缺少aClassLoader参数的方法时,将其设置为getClass().getClassLoader()

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

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

获取类加载器的任何其他方法都是错误的。如果库使用诸如Thread.getContextClassLoader(),sun.misc.VM.latestUserDefinedLoader()之类的黑客攻击,或者是sun.reflect.Reflection.getCallerClass()it是由API缺陷引起的错误。基本上,Thread.getContextClassLoader()只是因为设计了ObjectInputStreamAPI的人忘了接受ClassLoader作为参数,而这个错误一直困扰着Java社区。

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

首先使用这样的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);
}