17

有 1000 万篇关于Java 类加载器是什么以及如何/*为什么*编写自己的类加载器的文章和文档……但它们似乎都在假设一些我无法找到简单答案的事情!

我了解类加载器的工作:读取字节码并从中构造一个对象。不同的类加载器以不同的方式执行此操作,等等。

但是我从来不需要在我自己的代码中针对类加载器 API 编写代码,也从来不需要编写自己的代码,我很难理解自己的代码何时ClassLoader真正触发。

例如:

public static void main(String[] args) {
    Fizz fizz = new Fizz();
    fuzz.buzz();
}

在这里,我们有一个Fizz对象。在Fizz实例化 a 之前,我们需要一个类加载器来启动并加载Fizz.class到它的缓存中。这发生在何时何地?!?!它在我的代码中没有明确显示,所以它必须隐含在 JRE 中的某个地方......?

与该问题相切,如果我编写自己的类加载器,例如,WidgetClassLoader并希望将其配置为加载我的所有应用程序的类,或者只是我的Fizz.class,我如何将它“绑定”WidgetClassLoader到我的应用程序中,以便它知道要使用哪个类加载器? 我的代码需要显式调用这个类加载器还是像第一个示例一样隐含?提前致谢!

4

4 回答 4

7

你的问题不像你现在想的那么简单。

您的 Fizz 示例: Fizz 何时加载?这是在JLS(第 5.4 章:链接)中定义的。它没有定义何时加载 Fizz,但它保证了可见行为。对于'when'部分,如果找不到Fizz,则会从第一个访问Fizz的语句中抛出异常(Fizz fizz = new Fizz())。我很确定在这种情况下它会特别是新的 Fizz() 因为表达式的右侧首先被评估。如果你这样写:

Fizz fizz = null;
fizz = new Fizz();

在这种情况下, Fizz fizz = null 已经抛出异常,因为它是第一次访问 Fizz 类。

谁加载 Fizz?当必须加载一个类时,“属于”需要该类的代码的类加载器用于获取该类。在 Fizz 示例中,这将是使用 main 方法加载类的类加载器。当然,如果类加载器不能自己加载 Fizz,它可以选择委托给它的父类加载器。

如何让 JVM 使用我的ClassLoader?有两种方式,显式或隐式。明确地:您可以通过调用其方法通过您自己的类加载器加载一个类。隐含地:当您从已经从类加载器加载的类中执行代码(意味着方法或初始化程序)并且需要在该过程中解析类引用时,您的类加载器将被自动使用,因为它是加载代码的类加载器第一名。

于 2012-07-18T16:06:39.587 回答
3

Java 有一个默认的类加载器。这会在默认类路径中查找类声明。如果您编写自己的类加载器,您可以(并且应该)设置父类加载器。如果您没有其他人,这将是默认设置。如果你不这样做,你的类加载器将无法找到 java API 类。如果 java 寻找一个类,它不会从你的自定义类加载器开始寻找,而是从父类加载器开始。如果这个有父母,它会从那里开始,依此类推。只有在找不到类时,才会使用下一个子类加载器再次尝试。只要有孩子,这种情况就会继续。如果链中的任何加载器都找不到该类,ClassNotFoundException则抛出 a。

当然,如果您首先将其设置为默认类加载器(通过调用Thread.currentThread().setContextClassLoader())或手动加载类(通过调用loadClass()),java 只会使用您的类加载器。

我不确定何时调用类加载器。我认为它是在启动程序(在所有声明为的类import上)或第一次使用类(变量声明或构造函数调用)时调用的。

于 2012-07-18T15:46:56.330 回答
0

类的实际创建发生在defineClass. 该类是使用来自多个源中的任何一个的字节数组创建的。

到达defineClass(即protected)的正常路径是通过findClass(当然,也是protected)。所以通常的入口点是loadClass-> findClass-> defineClass。但是对于特殊情况还有其他途径。

(整个事情相当复杂,并且代表了随着保护变得更加复杂和访问模式更加多样化而添加层的历史。)

于 2012-07-18T16:17:04.787 回答
0

如果您对类加载器以及它们何时以及如何工作感兴趣,您还可以查看OSGi 规范——在我看来,这对您来说将是一本非常有趣的读物。OSGi 是一个Java 框架,它提供模块化、清晰的代码分离和生命周期管理,目前非常流行(例如Eclipse 本身就是基于一个)。

OSGi 大量使用类加载器,并且有一个非常好的解释何时以及如何在规范中发生所有与类加载有关的事情。基本上,它们为每个包都有一个单独的包类加载器(这就是模块的调用方式),这些类加载器负责依赖关系并从另一个包中获取正确的类。

于 2012-07-19T10:29:12.497 回答