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

也就是说,如果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()这样的hacks,这是由API中的缺陷引起的错误。基本上,Thread.getContextClassLoader()的存在只是因为设计ObjectInputStream API的人忘记接受ClassLoader作为参数,这个错误至今仍困扰着Java社区。

That said, many many JDK classes use one of a few hacks to guess some class loader to use. Some use the ContextClassLoader (which fails when you run different apps on a shared thread pool, or when you leave the ContextClassLoader null), some walk the stack (which fails when the direct caller of the class is itself a library), some use the system class loader (which is fine, as long as it is documented to only use classes in the CLASSPATH) or bootstrap class loader, and some use an unpredictable combination of the above techniques (which only makes things more confusing). This has resulted in much weeping and gnashing of teeth.

在使用这样的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);
}

其他回答

每个类将使用自己的类加载器来加载其他类。因此,如果class .class引用了class .class,那么ClassB需要在ClassA的类加载器或它的父类的类路径上。

线程上下文类加载器是当前线程的当前类加载器。对象可以从ClassLoaderC中的类创建,然后传递给ClassLoaderD拥有的线程。在这种情况下,对象需要直接使用Thread.currentThread(). getcontextclassloader()如果它想加载在自己的类加载器中不可用的资源。

infoworld.com上有一篇文章解释了这种区别 你应该使用哪个ClassLoader

(1)

Thread context classloaders provide a back door around the classloading delegation scheme. Take JNDI for instance: its guts are implemented by bootstrap classes in rt.jar (starting with J2SE 1.3), but these core JNDI classes may load JNDI providers implemented by independent vendors and potentially deployed in the application's -classpath. This scenario calls for a parent classloader (the primordial one in this case) to load a class visible to one of its child classloaders (the system one, for example). Normal J2SE delegation does not work, and the workaround is to make the core JNDI classes use thread context loaders, thus effectively "tunneling" through the classloader hierarchy in the direction opposite to the proper delegation.

(2)来源相同:

This confusion will probably stay with Java for some time. Take any J2SE API with dynamic resource loading of any kind and try to guess which loading strategy it uses. Here is a sampling: JNDI uses context classloaders Class.getResource() and Class.forName() use the current classloader JAXP uses context classloaders (as of J2SE 1.4) java.util.ResourceBundle uses the caller's current classloader URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only Java Serialization API uses the caller's current classloader by default

补充@David Roussel的回答,类可以由多个类装入器装入。

让我们了解类装入器是如何工作的。

来自javin paul博客中的javarevisited:

ClassLoader遵循三个原则。

代表团的原则

当需要时,在Java中装入类。假设你有一个应用程序特定的类叫做Abc.class,加载这个类的第一个请求将来到应用程序类加载器,它将委托给它的父类扩展类加载器,扩展类加载器进一步委托给原始类或引导类加载器

Bootstrap ClassLoader is responsible for loading standard JDK class files from rt.jar and it is parent of all class loaders in Java. Bootstrap class loader don't have any parents. Extension ClassLoader delegates class loading request to its parent, Bootstrap and if unsuccessful, loads class form jre/lib/ext directory or any other directory pointed by java.ext.dirs system property System or Application class loader and it is responsible for loading application specific classes from CLASSPATH environment variable, -classpath or -cp command line option, Class-Path attribute of Manifest file inside JAR. Application class loader is a child of Extension ClassLoader and its implemented by sun.misc.Launcher$AppClassLoader class.

注意:除了Bootstrap类加载器,它主要是用C语言实现的,所有Java类加载器都是使用Java .lang. classloader实现的。

可见性原则

根据可见性原理,Child ClassLoader可以看到Parent ClassLoader加载的类,反之则不行。

唯一性原则

根据这个原则,父类加载器加载的类不应该再被子类加载器加载

这并没有回答最初的问题,但是由于这个问题对于任何ContextClassLoader查询都是高度排序和链接的,所以我认为回答应该何时使用上下文类装入器的相关问题是很重要的。简单的回答:永远不要使用上下文类装入器!但是当你必须调用一个缺少ClassLoader参数的方法时,将它设置为getClass(). getclassloader()。

当一个类的代码请求加载另一个类时,要使用的正确类加载器是与调用类相同的类加载器(即getClass(). getclassloader())。这是事情99.9%的工作方式,因为这是JVM在您第一次构造新类的实例、调用静态方法或访问静态字段时所做的事情。

当您希望使用反射创建类时(例如反序列化或加载可配置的命名类时),执行反射的库应该始终通过从应用程序接收ClassLoader作为参数来询问应用程序使用哪个类装入器。应用程序(它知道所有需要构造的类)应该将getClass(). getclassloader()传递给它。

任何其他获取类装入器的方法都是不正确的。如果一个库使用了诸如Thread.getContextClassLoader()、sun.misc.VM.latestUserDefinedLoader()或sun.reflect.Reflection.getCallerClass()这样的hacks,这是由API中的缺陷引起的错误。基本上,Thread.getContextClassLoader()的存在只是因为设计ObjectInputStream API的人忘记接受ClassLoader作为参数,这个错误至今仍困扰着Java社区。

That said, many many JDK classes use one of a few hacks to guess some class loader to use. Some use the ContextClassLoader (which fails when you run different apps on a shared thread pool, or when you leave the ContextClassLoader null), some walk the stack (which fails when the direct caller of the class is itself a library), some use the system class loader (which is fine, as long as it is documented to only use classes in the CLASSPATH) or bootstrap class loader, and some use an unpredictable combination of the above techniques (which only makes things more confusing). This has resulted in much weeping and gnashing of teeth.

在使用这样的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);
}