小介绍
如您所知,默认情况下 Java 使用引导类加载器和系统类加载器。第一个负责加载引导类(其类路径包含诸如 rt.jar 之类的工件),第二个负责保存应用程序的类路径。通常,在您的环境变量中定义或在 JVM 中给出的类路径开始使用该-cp
参数。
答案
com.example.SomeClass
仅当发生以下两种情况之一时,您的自定义类加载器才会加载该类Custom
:要么在启动时定义自定义类加载器用作系统类加载器,要么在运行时通过它显式加载类。
关于每个选项的更多信息:
在应用程序启动时:您可以在启动 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的旧开源项目。即使这个项目很老,我还是建议它,因为它是一个小项目,充满了关于类加载机制的知识,你可以轻松地深入了解。