线程的上下文类加载器和普通的类加载器之间有什么区别?
也就是说,如果Thread.currentThread().getContextClassLoader()
和getClass().getClassLoader()
返回不同的类加载器对象,将使用哪一个?
答案 0 :(得分:135)
每个类都将使用自己的类加载器来加载其他类。因此,如果ClassA.class
引用ClassB.class
,则ClassB
需要位于ClassA
或其父级的类加载器的类路径中。
线程上下文类加载器是当前线程的当前类加载器。可以从ClassLoaderC
中的类创建对象,然后将其传递给ClassLoaderD
拥有的线程。在这种情况下,如果对象需要加载其自己的类加载器上不可用的资源,则需要直接使用Thread.currentThread().getContextClassLoader()
。
答案 1 :(得分:85)
有一篇关于javaworld.com的文章解释了它的不同之处 => Which ClassLoader should you use
(1)
线程上下文类加载器提供了一个 后门周围的班级 授权计划。
以JNDI为例:它的胆量是 由bootstrap类实现 rt.jar(从J2SE 1.3开始),但是 这些核心JNDI类可能会加载JNDI 提供者由独立实施 供应商并可能部署在 应用程序的-classpath。这个 场景调用父级 classloader(原始的) 这种情况)加载一个可见的类 它的一个子类加载器( 例如,系统一)。正常的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默认使用调用方的当前类加载器
答案 2 :(得分:82)
这不能回答原始问题,但由于问题是针对任何ContextClassLoader
查询进行高度排名和链接的,我认为回答关于何时应该使用上下文类加载器的相关问题非常重要。简短回答:从不使用上下文类加载器!但是,当您必须调用缺少getClass().getClassLoader()
参数的方法时,请将其设置为ClassLoader
。
当一个类的代码要求加载另一个类时,要使用的正确类加载器与调用者类(即getClass().getClassLoader()
)是相同的类加载器。这是99.9%的时间工作的方式因为this is what the JVM does itself第一次构造新类的实例,调用静态方法或访问静态字段。
如果要使用反射创建类(例如反序列化或加载可配置的命名类),执行反射的库应始终要求应用程序使用哪个类加载器,从应用程序接收ClassLoader
作为参数。应用程序(知道需要构建的所有类)应该传递它getClass().getClassLoader()
。
获取类加载器的任何其他方法都是错误的。如果某个库使用诸如Thread.getContextClassLoader()
,sun.misc.VM.latestUserDefinedLoader()
或sun.reflect.Reflection.getCallerClass()
之类的黑客攻击,那么这是由API不足引起的错误。基本上,Thread.getContextClassLoader()
的存在只是因为设计ObjectInputStream
API的人忘记接受ClassLoader
作为参数,而这个错误一直困扰着Java社区。 p>
也就是说,许多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);
}
答案 3 :(得分:32)
添加到@David Roussel答案,类可以由多个类加载器加载。
让我们了解class loader的工作原理。
来自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属性。
应用程序类加载器是 Extension ClassLoader 的子项,由sun.misc.Launcher$AppClassLoader
类实现。
注意: Bootstrap类加载器除外,它主要用C语言实现,所有Java类加载器都是使用java.lang.ClassLoader
实现的。
根据可见性原则, Child ClassLoader 可以看到由 Parent ClassLoader 加载的类,但反之亦然。
根据这个原则,父类加载的类不应再次由Child ClassLoader加载