线程的上下文类加载器和普通类加载器的区别
线程的上下文类加载器和普通类加载器的区别
线程的上下文类加载器和普通类加载器有什么区别?
即,如果 Thread.currentThread().getContextClassLoader()
和 getClass().getClassLoader()
返回不同的类加载器对象,哪一个将被使用?
这篇文章并不是在回答原本的问题,但由于这个问题在任何关于ContextClassLoader
的查询中排名都很高并且被链接,因此我认为回答相关问题:在什么情况下应该使用上下文类加载器,是很重要的。简短的回答是:永远不要使用上下文类加载器!但是当你必须要调用缺失了ClassLoader
参数的方法时,将它设置为getClass().getClassLoader()
。
当一个类的代码请求加载另一个类时,正确的类加载器应该是调用者类相同的类加载器(即getClass().getClassLoader()
)。99.9%的情况下,这就是事情的工作方式,因为这是JVM自己在你构造一个新类的实例、调用静态方法或访问静态字段时所做的。
当你想要使用反射创建一个类(例如当反序列化或加载一个可配置的命名类时),执行反射的库应该总是询问应用程序使用哪个类加载器,即从应用程序收到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
中的类即可),或引导类加载器,还有些使用以上技术的不可预测的组合(这只会使事情更加混乱)。这导致了人们的大量哭泣和牙 gnashing。
在使用这样的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); }