线程的上下文类加载器和普通的类加载器有什么区别?
即如果Thread.currentThread().getContextClassLoader()
和getClass().getClassLoader()
返回不同的类加载器对象,会使用哪一个呢?
这并没有回答原始问题,但由于该问题对于任何 ContextClassLoader
查询的排名和链接都很高,我认为回答上下文类加载器何时应该是相关问题很重要用过的。简短的回答:永远不要使用上下文类加载器!但是,当您必须调用缺少 ClassLoader
参数的方法时,请将其设置为 getClass().getClassLoader()
。
当一个类的代码要求加载另一个类时,要使用的正确类加载器是与调用者类相同的类加载器(即,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 社区至今。
也就是说,许多 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);
}
我是一名优秀的程序员,十分优秀!