自从我开始学习 Java 以来,我几乎就知道
清单
文件中的
Class-Path 标头字段
指定
可执行 JAR 的相对运行时
类路径(JAR 的
应用程序起点
由另一个名为
Main-Class
的清单标头指定)。一位
同事
最近遇到了一个让我感到惊讶的问题,因为它证明了当运行
javac
时包含 JAR 包含在类路径中时,
JAR
文件的清单
Class-Path
路径条目也会影响编译时类路径。这篇文章展示了这种新的细微差别。
The Java Tutorials
的
Deployment Trail
的“
Adding Classes to the JAR File's Classpath
”部分指出,“您指定要包含在 applet 或应用程序的清单文件中的
Class-Path
标头字段中的类。”同一部分还指出,“通过在清单中使用
Class-Path
标头,您可以避免在调用 Java 运行您的应用程序时必须指定长类
-classpath
标志。”这两句话基本上总结了我一直以来对清单文件中
Class-Path
标头的看法:作为通过 Java 应用程序启动器(
java
可执行文件)执行的包含 JAR 的类路径。
事实证明,JAR 清单中的
Class-Path
条目会影响 Java 编译器 (
javac
),就像它会影响 Java 应用程序启动器 (
java
) 一样。为了演示这一点,我将使用一个简单的接口 (
PersonIF
)、一个实现该接口的简单类 (
Person
) 以及一个使用实现该接口的类的简单类
Main
。接下来显示这些代码清单。
PersonIF.java
public interface PersonIF
{
void sayHello();
}
人.java
public interface PersonIF
{
void sayHello();
}
主.java
public interface PersonIF
{
void sayHello();
}
从上面的代码清单可以看出,
Main
类依赖于(使用)
Person
类,而
Person
类依赖于(实现)
PersonIF
。我将有意将
PersonIF.class
文件放在它自己的名为
PersonIF.jar
的 JAR 中,并将该 JAR 存储在(不同的)子目录中。
Person.class
文件将存在于它自己的
Person.jar
JAR 文件中,并且该 JAR 文件包含一个
MANIFEST.MF file
,该文件
Class-Path
标头在相关子目录中引用
PersonIF.jar
。
我现在将尝试仅使用类路径上的当前目录从
Main.java
编译
Main.class
。当
javac
无法在单独的子目录中找到
PersonIF.jar
时,我以前预计编译会失败。但是,它不会失败!
这让我感到惊讶。为什么在我没有明确指定
PersonIF.class
(或包含它的 JAR)作为通过
-cp
标志提供的类路径的值时编译?可以通过运行带有
-verbose
标志的
javac
来查看答案。
javac -verbose
的输出提供了“
源
文件的搜索路径”和“
类
文件的搜索路径”。 “类文件的搜索路径”在本例中很重要,因为我已将
PersonIF.java
和
Person.java
源文件移动到一个完全不相关的目录,而不是在那些指定的搜索路径中。有趣的是,类文件的搜索路径(以及源文件的搜索路径)包括
archive/PersonIF.jar
,即使我没有在
-cp
的值中指定这个 JAR(甚至它的目录)。这表明
Oracle 提供的 Java 编译器
考虑类路径上指定的任何 JAR 的
MANIFEST.MF
的
Class-Path
路径标头中指定的类路径内容。
下一个屏幕快照演示了运行新编译的
Main.class
类并从
archive/PersonIF.jar
中获取依赖项
PersonIF.class
,而没有在传递给 Java 应用程序启动器的
java -cp
标志的值中指定它。我希望运行时行为是这样的,尽管我从来没有尝试过,甚至没有想过用一个
MANIFEST.MF
文件没有
Main-Class
头文件(不可执行的 JAR)的 JAR 来做。此示例中的
Person.jar
清单文件未指定
Main-Class
标头,仅指定了
Class-Path
标头,但在使用
java
调用时仍然能够在运行时使用此类路径内容。
这篇文章的最后一个演示涉及从 JAR 文件中删除
Class-Path
标头和关联值,并尝试使用
javac
和相同的命令行指定类路径进行编译。在这种情况下,包含
Person.class
的 JAR 称为
Person2.jar
,下面的屏幕快照表明其
MANIFEST.MF
文件没有
Class-Path
标头。
下一个屏幕快照表明现在使用
javac
进行编译失败,因为正如预期的那样,
PersonIF.class
没有在类路径上明确指定,并且不再通过类路径上的 JAR 的
MANIFEST.MFClass-Path
标头引用提供.
我们从前面的屏幕快照中看到,源文件和类文件的搜索路径不再包括
archive/PersonIF.jar
。如果没有可用的 JAR,
javac
将无法找到
PersonIF.class
并报告错误消息:“找不到 PersonIF 的类文件”。
一般观察
-
MANIFEST.MF
文件中的Class-Path
标头不依赖于存在于同一 JAR 的MANIFEST.MF
文件中的Main-Class
标头。-
带有
Class-Path
清单标头的 JAR 将使这些类路径条目可供 Java 类加载器使用,而不管该 JAR 是使用java -jar ...
执行的还是只是放置在更大的 Java 应用程序的类路径中。 -
如果 JAR 包含在为 Java 编译器指定
Class-Path
中,则具有类路径清单标头的 JAR 将使这些类路径条目可供 Java 编译器 (javac
) 使用。
-
带有
-
因为在 JAR 的清单文件中使用
Class-Path
的范围不限于正在执行Main-Class
JAR,所以这些类依赖可能会无意中满足(甚至可能使用不正确的版本),而不是解析明确指定的类路径条目.使用Class-Path
或使用清单文件中指定Class-Path
的第三方 JAR 时,建议谨慎。 - JAR 的清单文件的重要性有时被低估了,但是这个主题提醒我们 了解特定 JAR 的清单文件中的内容 的有用性。
-
本主题提醒您可以通过不时地使用
-verbose
标志运行javac
来了解它的含义。 -
每当您将 JAR 放置在
javac
编译器或java
应用程序启动器的类路径中时,您放置的不仅仅是该 JAR 中的类定义。您还将将该 JAR 的清单的Class-Path
引用的任何类和 JAR 放在编译器或应用程序启动器的类路径上。
结论
Java 类加载器可以从许多地方加载用于构建和运行 Java 应用程序的类。正如这篇文章所展示的,JAR 的
MANIFEST.MF
文件的
Class-Path
标头是影响类加载器将在运行时和编译时加载哪些类的另一个接触点。
Class-Path
的使用不仅影响“可执行”的 JAR(在其清单文件中指定
Main-Class
标头并使用
java -jar ...
运行),而且会影响编译加载的类和任何Java 应用程序执行,其中带有包含
Class-Path
标头的清单文件的 JAR 位于类路径中。