如何选择Classloader?

动机

假设我们有一个类加载层次结构,如下所示:

Bootstrap | System | Custom 

假设Custom Classloader用于加载类com.example.SomeClass 。 它检查System类加载器是否可以加载它,再次检查Bootstrap类加载器是否可以加载它。 由于两者都不能, com.example.SomeClassCustom classloader加载。

com.example.SomeClass依赖的任何类都是相同的。 我相信我理解这个过程。

我不明白为什么Custom会首先尝试加载com.example.SomeClass 。 如何在Java应用程序中选择当前的类加载器?

小介绍

如您所知,默认情况下,Java使用引导类加载器和系统类加载器。 第一个负责加载引导类(它的类路径包含诸如rt.jar之类的工件),第二个负责保存应用程序的类路径。 通常,在环境变量中定义或在JVM中给出的类路径都使用-cp参数开始。

答案

只有在发生以下两种情况之一时,才会由自定义类加载器Custom加载类com.example.SomeClass :要么在启动时定义自定义类加载器以用作系统类加载器,要么在运行时通过它显式加载类。

关于每个选项的更多信息:

  • 在应用程序启动时 :您可以定义何时启动JVM实例,而不是使用Java的默认系统类加载器,而是使用您自己的。 为此,只需使用定义的以下环境变量调用java:

     -Djava.system.class.loader=my.tests.classloaders.Custom 

    在这种情况下,会发生的情况是,该JVM实例中的应用程序中的所有类实际上都将由Custom类加载器加载。

  • 在运行时 :您可以在运行时使用自定义类加载器加载类。 这是通过创建自定义类加载器的实例并从中加载类来实现的

      ClassLoader classloader = new CustomClassLoader(); Class someClass = classloader.loadClass("com.example.SomeClass"); 

就像@Noofiz在他的回答中所说,一旦你加载了一个类,所有需要但尚未加载的引用类都是通过相关的类加载器加载的。 因此,如果使用自定义类加载器加载一个类,则所有引用的类也将通过它加载。 加载所有类时,您可以执行任何操作,记录正在加载的类,委托父类加载器,自己加载类…

一些额外的信息

通常,实现自定义类加载器的最佳方法是使用您提到的委派模型。 这是因为类实际上不仅由类的字节码定义,而且还由其类加载器定义,这意味着由两个不同的类加载器加载的类将不是同一个类

这意味着当您的自定义类加载器委托给它的父级时,您确保该类可用于更广泛的范围 。 大多数情况下,这将是你想要的,但并非总是如此。

如果由于某种原因你想要类隔离,那么你的自定义类加载器可能会以相反的方式实现。 首先,它尝试自己加载类,并且只有在找不到类(或者是JVM系统类或您可能想要跳过的任何其他类)时才将其委托给它的父级。 例如,Web应用程序容器以这种方式工作,允许上下文重新部署(基本上它们丢弃类加载器并创建一个新的加载所有内容)和Web应用程序之间的完全类隔离。

正如我已经说过的那样,处理类加载并不是微不足道的,要么你真的知道你在做什么,要么你肯定会发现自己陷入了一些奇怪的伏都教问题。

也许已经过于偏离主题了,但是如果你想要更多关于类加载器和隔离的手,你可以检查一个名为classworlds的旧开源项目。 即使这个项目很老,我也建议它,因为它是一个小项目,充满了关于类加载机制的知识,你可以很容易地深入研究。

每个类都是第一次在某种方法中被请求,每个方法都是某个类的一部分,它已经被加载并且已经定义了它的类加载器。 因此,当需要一个新类时,它会通过curent方法的类的类加载器查找。 如果通过自定义类加载器加载类,它将成为由此类方法加载的所有类的基类加载器。 JVM规范没有定义如何静态地解析类(在启动时加载所有图形),或动态地(在第一次请求时)。 但是静态加载需要太长时间才能使用它,并且当应用程序已经运行时我们会收到ClassNotFoundError。 类和接口解析