繁体   English   中英

Java:编译器和JRE是否需要访问所有第三方类文件?

[英]Java: Do BOTH the compiler AND the JRE require access to all 3rd-party class files?

我有15年的C ++经验,但我不熟悉Java。 我试图了解Java如何处理头文件的缺失。 我有几个与此问题相关的问题。

具体来说,假设我为导入第三方类'Z'(并使用Z)的类'A'编写源代码。 据我所知,在编译时,Java编译器必须“访问”有关Z的信息才能编译A.java,创建A.class。 因此,Z.java或Z.class(或包含其中一个的JAR;比如Z.jar)必须在编译时出现在本地文件系统上 - 对吗?

编译器是否使用类加载器加载Z(重复 - 在编译时)?

如果我在COMPILE时使用类加载器是正确的,那么如果需要用户定义的类加载器(L)并且是正在编译的项目的一部分呢? 例如,假设L负责通过网络下载Z.class AT RUNTIME? 在这种情况下,Java编译器将如何在编译时获取Z.class? 它会先尝试编译L,然后在编译时使用L来获取Z吗?

据我所知,使用Maven构建项目,Z.jar可以在编译时通过Internet定位在远程存储库中 - 在ibiblio上,或在POM文件中定义的自定义存储库中。 我希望我是正确的,它是MAVEN负责在编译时下载第三方JAR文件,而不是编译器的JVM?

但请注意,在RUNTIME,A.class再次需要Z.class - JRE将如何知道从哪里下载Z.class(没有Maven帮助)? 或者开发人员有责任将Z.class与A.class一起发布到应用程序(例如在JAR文件中)? (...假设未使​​用用户定义的类加载器。)

现在一个相关的问题,只是为了确认:我假设一旦编译,A.class只包含到Z.class的符号链接 - Z.class的字节码不是A.class的一部分; 如果我错了,请纠正我。 (在C ++中,静态链接会将字节从Z.class复制到A.class中,而动态链接则不会。)

关于编译过程的另一个相关问题:一旦描述Z的必要文件在编译时位于CLASSPATH上,编译器是否需要Z.class中的字节码才能编译A.java(如果需要,将构建Z.class) ,来自Z.java),还是Z.java足以满足编译器的需要?

我的整体困惑可归纳如下。 似乎Z的完整[byte]代码需要存在TWICE - 一次在编译期间,第二次在运行时 - 并且对于Java程序引用的所有类必须为true。 换句话说,每个类必须下载/呈现TWICE。 在编译期间,不能将单个类表示为头文件(因为它可以在C ++中)。

编译器是否使用类加载器加载Z(重复 - 在编译时)?

几乎。 它使用JavaFileManager ,它在很多方面充当类加载器。 它实际上并没有加载类,因为它需要从.java文件和.class文件创建类签名。

我希望我是正确的,它是MAVEN负责在编译时下载第三方JAR文件,而不是编译器的JVM?

是的,虽然可以实现一个行为类似于URLClassLoader的JavaFileManager,但是Maven可以下载jar。 Maven管理jar的本地缓存,并根据需要从网络填充缓存。

关于编译过程的另一个相关问题:一旦描述Z的必要文件在编译时位于CLASSPATH上,编译器是否需要Z.class中的字节码才能编译A.java(如果需要,将构建Z.class) ,来自Z.java),还是Z.java足以满足编译器的需要?

它不需要所有字节码。 只是类,方法和属性签名和元数据。 如果A依赖于Z,则可以通过在源路径上找到的Z.java,在任何(类路径,系统类路径)上找到的Z.class或通过某些自定义扩展(如Z)来满足该依赖关系。 JSP。

我的整体困惑可归纳如下。 似乎Z的完整[byte]代码需要存在TWICE - 一次在编译期间,第二次在运行时 - 并且对于Java程序引用的所有类必须为true。 换句话说,每个类必须下载/呈现TWICE。 在编译期间,不能将单个类表示为头文件(因为它可以在C ++中)。

也许一个例子可以帮助澄清这一点。 java语言规范要求编译器进行某些优化。 内联static final原始StringString

如果A类仅依赖于B作为常数:

class B {
  public static final String FOO = "foo";
}

class A {
  A() { System.out.println(B.FOO); }
}

然后可以在类路径上编译,加载和实例化A而不使用B.class 如果您更改并发送了具有不同FOO值的B.class ,则A仍将具有该编译时依赖性。

因此,可以具有编译时依赖性而不是链接时依赖性。

当然,通过反射可以获得没有编译时依赖性的运行时依赖性。

总而言之,在编译时,编译器确保类访问的方法和属性可用。

在类加载时(运行时),字节码验证器检查预期的方法和属性是否真的存在。 因此,字节码验证器会仔细检查编译器所做的假设(内联假设除外)。

可以模糊这些区别。 例如,JSP使用一个自定义类加载器来调用java编译器,以便在运行时根据需要从源代码编译和加载类。

理解Maven如何适应图片的最好方法是意识到它(大部分)没有。

Maven不参与编译器查找定义的过程,或者运行时系统加载类。 编译器本身就是这样做的...基于构建时类路径所说的内容。 当您运行该应用程序时,Maven根本不再处于图片中。

在构建时,Maven的作用是检查在POM文件中声明的项目依赖项,检查版本,下载缺少的项目,将JAR放在一个众所周知的位置,并为编译器(和其他工具)创建一个“类路径”。

然后,编译器从这些JAR文件中“加载”它所需的类,以在已编译的类文件中提取类型签名信息。 它不使用常规类加载器来执行此操作,但用于查找类的基本算法是相同的。

编译完成后,Maven会负责打包到JAR,WAR,EAR文件等,这些都是由POM文件指定的。 对于WAR或EAR文件,将所有必需的从属JAR打包到文件中。

没有Maven导向的JAR下载在运行时发生。 但是,运行应用程序可能涉及下载JAR文件; 例如,如果使用Java WebStart部署应用程序。 (但在这种情况下,JAR不会从Maven存储库下载...)

还有一些事情需要注意:

  • Maven根本不需要在图片中。 您可以使用IDE来构建构建,Ant构建工具(可能是Ivy),Make甚至是“哑”shell脚本。 根据构建机制,您可能需要手动处理外部依赖项; 例如,找出外部JAR下载,放置它们等等。

  • Java运行时系统通常必须加载比编译器更多的内容。 编译器只需要加载那些类型检查正在编译的类所必需的类。

    例如,假设类A有一个使用类B作为参数的方法,而类B有一个使用类C作为参数的方法。 在编译A ,需要加载B ,而不是C (除非A在某种程度上直接依赖于C )。 执行A ,需要加载BC

    第二个例子,假设A类依赖于接口I和实现IC1IC2 除非A明确依赖于IC1IC2 ,否则编译器不需要加载它们来编译A

  • 也可以在运行时动态加载类; 例如,通过调用Class.forName(className) ,其中className是一个字符串值表达式。


你写了:

对于第二个要点中的示例 - 我认为开发人员可以选择在编译时为B提供一个不包含使用C的B方法的存根文件,并且A可以编译得很好。 这将证实我的评估,在编译时,在Java中完全允许所谓的“头”文件,其中只声明了必要的函数(即使是存根) - 所以这只是为了方便/约定,工具随着时间的推移不再发展使用标头/源文件区分。 (如我错了请纠正我。)

它不是一个方便/进化的东西。 Java从未支持单独的头文件。 詹姆斯·戈斯林等人从头文件和预处理器是一个坏主意的立场开始。

你假设的B版存根版必须拥有真实B所有可见方法,构造函数和字段,并且方法和构造函数必须具有实体。 存根B不会编译。 (我猜在理论上,主体可能是空的,返回虚拟值或抛出未经检查的异常。)

这种方法的问题在于它会非常脆弱 如果您在保持的存根和完整版本做出的最小的错误B步骤,其结果必然是类装载器(在运行时)会报告一个致命的错误。

顺便说一句,C和C ++几乎是拥有单独头文件的例外。 在支持单独编译的大多数其他语言中(包括应用程序的不同文件),编译器可以从实现源代码中提取接口信息(例如,签名)。

另外一个可以帮助的拼图,接口和抽象类也被编译成类文件。 因此,在编译A时,理想情况下,您将针对API进行编译,而不一定是具体类。 因此,如果A在编译时使用接口B(由Z实现),则需要A和B的类文件,但在运行时,您需要A,B和Z的类文件。所有类都是动态链接的(正确的)可以破解文件并查看字节码并查看其中的完全限定名称。如果你很好奇, jclasslib是一个很好的检查类文件和读取字节码的工具。 我可以在运行时替换类。 但是运行时的问题通常会导致各种形式的LinkageErrors

通常,如果一个类与您编译的jar文件一起发布,则决定取决于您的特定方案。 假定在每个JRE实现中都有可用的类。 但是,如果我有自己的API和实现,我将不得不以某种方式提供它们运行的​​任何地方。 有一些API尽管例如servlet的 ,我会针对编译该servlet API,但容器(例如的Websphere)负责提供该servlet API和实现在运行时,我(因此我不应该出货我自己的这些副本)。

我有15年的C ++经验,但我不熟悉Java。

您可能面临的最大挑战是许多在C ++中被视为重要的东西,例如sizeof()一个对象,无符号整数和析构函数,在Java中并不容易做到,并且没有被视为具有相同的重要性和有其他解决方案/解决方案。

我试图了解Java如何处理头文件的缺失。 我有几个与此问题相关的问题。

Java有一些概念与头文件类似的接口,因为它们只包含没有定义的声明(和常量)。 类通常与该类的接口配对,有时一对一。

编译器是否使用类加载器加载Z(重复 - 在编译时)?

当类加载器加载一个类时,它会调用静态初始化块,它可以执行任何操作。 所有编译器需要的是从类中提取元数据,而不是字节代码,这就是它的作用。

是MAVEN负责在编译时下载第三方JAR文件,而不是编译器的JVM?

Maven必须将文件加载到本地文件系统,默认位置为~/.m2/repository

JRE如何知道从哪里下载Z.class(没有Maven帮助)?

它必须要么使用Maven; 一些OSGi容器能够动态加载和卸载不同的版本,例如,您可以在正在运行的系统中更改库的版本,或从maven构建更新SNAPSHOT。

或者你有一个独立的应用程序; 使用像appassembly这样的Maven插件,您可以创建批处理/ shell脚本以及包含所需库的副本的目录。

或者是一个包含元信息和其中的许多jar的web存档war (它只是一个装有罐子的罐子;)

或者,开发人员有责任将Z.class与A.class一起发布到应用程序中

对于独立应用程序是。

现在是一个相关的问题,仅供确认:我假设一旦编译,A.class只包含指向Z.class的符号链接

从技术上讲,它只包含带有Z字符串,而不包含.class本身。 你可以改变很多Z而不再编译A,它仍然可以工作。 例如,你可以编译一次版本的Z并稍后用另一个版本替换它,应用程序仍然可以运行。 您甚至可以在应用程序运行时替换它。 ;)

Z.class的字节码不是A.class的一部分;

编译器旁边没有优化。 唯一重要的一个是IMHO,它是内联编译时常量。 这意味着如果在编译A后更改Z中的常量,则在A中可能不会更改。(如果在编译时使常量未知,则不会内联它)

没有内联字节代码,字节代码中的本机代码在运行时根据程序实际运行的方式进行内联。 例如,假设您有一个具有N个实现的虚拟方法。 C ++编译器不知道哪些内联esp,因为它们在编译时可能不可用。 但是,JVM可以看到哪些是最常用的(它在程序运行时收集统计信息)并且可以内联两个最常用的实现。 (关于在运行时删除/更新其中一个类时会发生什么的思考的食物;)

如果我错了,请纠正我。 (在C ++中,静态链接会将字节从Z.class复制到A.class中,而动态链接则不会。)

Java只有动态链接,但这并不妨碍在运行时内联代码,这与使用宏一样有效。

关于编译过程的另一个相关问题:一旦描述Z的必要文件在编译时位于CLASSPATH上,编译器是否需要Z.class中的字节码才能编译A.java(如果需要,将构建Z.class) ,来自Z.java),还是Z.java足以满足编译器的需要?

编译器将根据需要编译所有.java文件。 您只需要提供.java但它必须编译(即它的依赖项必须可用)但是如果您使用.class文件并不是所有的依赖都需要可用于编译A.

我的整体困惑可归纳如下。 似乎Z的完整[byte]代码需要存在TWICE - 一次在编译期间,第二次在运行时 -

从技术上讲,类包含字节码和元数据,例如方法签名,字段和常量。 在编译时不使用字节代码,只使用元信息。 编译时的字节代码不需要与运行时使用的字节代码匹配。 (使用的签名/字段可以)每个类的一个副本更简单,但如果您出于某种目的需要,可以在编译时使用精简版本。

并且对于Java程序引用的所有类必须为true。 换句话说,每个类必须下载/呈现TWICE。 在编译期间,不能将单个类表示为头文件(因为它可以在C ++中)。

它只需要下载一次,因为它位于存储库中或某些磁盘上。 像标题这样的接口可能就是你在编译时需要的所有东西,它们可能是一个单独的库,但通常它并不是因为在大多数情况下只有一个存档更简单(OSGi是我知道它在哪里的唯一例子)值得分开他们)

你的摘要是正确的,但我想补充一点,如果你编译成一个jar,那么jar将包含Z(如果Z是一个jar,只需要Z jar中需要的文件。

但是,相同的Z可用于编译和运行时。

简单地说,不。 如果你看一下说JDBC代码,它是针对一个接口编译的,为此目的就像一个头文件,并使用反射在运行时引入正确的实现。 驱动程序根本不需要在构建机器上存在,尽管现在更简洁的方法是通过依赖注入框架。

在任何情况下,没有什么能阻止你编译一个'header'类文件,然后针对实际的类文件运行(Java 主要是动态链接的),但这似乎为你自己做了额外的工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM