线程的上下文类装入器和普通类装入器之间的区别

2022-08-31 05:16:22

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

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


答案 1

这并没有回答原始问题,但是由于该问题对于任何查询都是高度排名和链接的,因此我认为回答何时应使用上下文类装入器的相关问题非常重要。简短的回答:永远不要使用上下文类装入器!但将其设置为必须调用缺少参数的方法时。ContextClassLoadergetClass().getClassLoader()ClassLoader

当一个类中的代码要求装入另一个类时,要使用的正确类装入器是与调用方类相同的类装入器(即 )。这是 99.9% 时间的工作方式,因为当您首次构造新类的实例、调用静态方法或访问静态字段时,JVM 会这样做getClass().getClassLoader()

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

获取类装入器的任何其他方法都是不正确的。如果一个库使用诸如Thread.getContextClassLoader()sun.misc.VM.latestUserDefinedLoader()sun.reflect.Reflection.getCallerClass()之类的黑客,那么它就是一个由API缺陷引起的错误。基本上,存在只是因为设计API的人忘记接受它作为参数,而这个错误至今一直困扰着Java社区。Thread.getContextClassLoader()ObjectInputStreamClassLoader

也就是说,许多JDK类使用少数几个技巧之一来猜测某些类加载器的使用。有些人使用(当您在共享线程池上运行不同的应用程序时会失败,或者当您离开时),有些人会遍历堆栈(当类的直接调用者本身就是一个库时会失败),有些人使用系统类加载器(这很好,只要它被记录为仅使用)或引导类加载器, 有些人使用上述技术的不可预测的组合(这只会使事情变得更加混乱)。这导致了大量的哭泣和咬牙切齿。ContextClassLoaderContextClassLoader nullCLASSPATH

使用此类 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);
}

答案 2

每个类将使用自己的类装入器来装入其他类。因此,如果引用需要位于 的类装入器或其父级的类路径上。ClassA.classClassB.classClassBClassA

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