Answers:
每个类将使用其自己的类加载器来加载其他类。所以,如果ClassA.class
引用ClassB.class
则ClassB
需要上的类加载器的类路径ClassA
,或者它的父母。
线程上下文类加载器是当前线程的当前类加载器。可以从中的类创建对象ClassLoaderC
,然后将其传递给拥有的线程ClassLoaderD
。在这种情况下,Thread.currentThread().getContextClassLoader()
如果对象要加载其自己的类加载器上不可用的资源,则需要直接使用。
ClassA.class
引用ClassB.class
” 是什么意思?
这不能回答原始问题,但是由于该问题在任何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()
之所以存在,是因为设计该ObjectInputStream
API的人忘记了接受ClassLoader
as作为参数,而这一错误一直困扰着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);
}
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默认使用调用方的当前类加载器
bootstrap
类加载器设置为上下文类加载器,而是将其设置为子system
类路径类加载器Thread
。JNDI
然后,这些类将确保用于Thread.currentThread().getContextClassLoader()
加载类路径上可用的JNDI实现类。
除了@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加载
ClassB
必须在ClassA
的loader(或ClassA
的loader的父母)的类路径上?ClassA
的加载程序是否有可能重写loadClass()
,ClassB
即使ClassB
它不在类路径中也可以成功加载?