Java中的编译时依赖和运行时依赖有什么区别?它与类路径有关,但它们有何不同?
7 回答
编译时依赖:你需要你的依赖
CLASSPATH
来编译你的工件。它们的产生是因为您对代码中硬编码的依赖项有某种“引用”,例如调用new
某个类、扩展或实现某些东西(直接或间接),或者使用直接reference.method()
表示法的方法调用。运行时依赖:你需要你的依赖
CLASSPATH
来运行你的工件。它们的产生是因为您执行访问依赖项的代码(以硬编码方式或通过反射或其他方式)。
尽管编译时依赖通常意味着运行时依赖,但您可以只拥有编译时依赖。这是基于 Java 仅在第一次访问该类时链接类依赖项的事实,因此如果您在运行时从不访问特定类,因为从未遍历代码路径,Java 将忽略该类及其依赖项。
这个例子
在 C.java 中(生成 C.class):
package dependencies;
public class C { }
在 A.java 中(生成 A.class):
package dependencies;
public class A {
public static class B {
public String toString() {
C c = new C();
return c.toString();
}
}
public static void main(String[] args) {
if (args.length > 0) {
B b = new B();
System.out.println(b.toString());
}
}
}
在这种情况下,对throughA
有编译时依赖,但如果在执行时传递一些参数,它只会对 C 有运行时依赖,因为 JVM 只会尝试解决对何时执行的依赖. 此功能允许您在运行时仅提供您在代码路径中使用的类的依赖关系,而忽略工件中其余类的依赖关系。C
B
java dependencies.A
B
C
B b = new B()
一个简单的例子是查看像 servlet api 这样的 api。要编译 servlet,您需要 servlet-api.jar,但在运行时 servlet 容器提供 servlet api 实现,因此您无需将 servlet-api.jar 添加到运行时类路径。
编译器需要正确的类路径才能编译对库的调用(编译时依赖项)
JVM 需要正确的类路径才能加载您正在调用的库中的类(运行时依赖项)。
它们可能在以下几个方面有所不同:
1)如果你的类C1调用库类L1,而L1调用库类L2,那么C1对L1和L2有运行时依赖,而对L1只有编译时依赖。
2) 如果你的类 C1 使用 Class.forName() 或其他机制动态实例化接口 I1,并且接口 I1 的实现类是类 L1,那么 C1 对 I1 和 L1 有运行时依赖,但只有编译时依赖在 I1 上。
编译时和运行时相同的其他“间接”依赖项:
3)你的C1类扩展库类L1,L1实现接口I1并扩展库类L2:C1对L1、L2和I1有编译时依赖。
4) 你的类 C1 有一个方法foo(I1 i1)
和一个方法bar(L1 l1)
,其中 I1 是一个接口,L1 是一个类,它接受一个接口 I1 的参数:C1 对 I1 和 L1 有编译时依赖性。
基本上,要做任何有趣的事情,您的类需要与类路径中的其他类和接口进行交互。由该组库接口形成的类/接口图产生了编译时依赖链。库实现产生运行时依赖链。请注意,运行时依赖链是运行时依赖或失败缓慢的:如果 L1 的实现有时依赖于实例化 L2 类的对象,并且该类仅在一种特定情况下被实例化,那么除了在那个场景。
Java 在编译时实际上并没有链接任何东西。它仅使用在 CLASSPATH 中找到的匹配类来验证语法。直到运行时,所有东西才被放在一起并基于当时的 CLASSPATH 执行。
编译时依赖项只是您在正在编译的类中直接使用的依赖项(其他类)。运行时依赖项涵盖了您正在运行的类的直接和间接依赖项。因此,运行时依赖项包括依赖项的依赖项和任何反射依赖项,例如您在 a 中拥有String
但在Class#forName()
.
对于 Java,编译时依赖是源代码的依赖。例如,如果 A 类从 B 类调用一个方法,那么 A 在编译时依赖于 B,因为 A 必须知道要编译的 B(B 的类型)。这里的诀窍应该是:编译后的代码还不是完整的可执行代码。它包括尚未编译或存在于外部 jar 中的源的可替换地址(符号、元数据)。在链接期间,这些地址必须替换为内存中的实际地址。要正确执行此操作,应创建正确的符号/地址。这可以通过类 (B) 的类型来完成。我相信这是编译时的主要依赖项。
运行时依赖与实际的控制流更相关。它涉及实际的内存地址。这是您在程序运行时所拥有的依赖项。您需要此处的 B 类详细信息,例如实现,而不仅仅是类型信息。如果该类不存在,那么您将得到 RuntimeException 并且 JVM 将退出。
这两个依赖项,通常和不应该,流向相同的方向。这是一个面向对象设计的问题。
在 C++ 中,编译有点不同(不仅仅是即时编译),但它也有一个链接器。所以我猜这个过程可能被认为类似于Java。
From @Jason S answer I derive mine with other words, in case it helps:
A runtime dependency of an app is actually a dependency (let's call it L2) of a compile-time dependency (L1) of this app. It may not be declared as a dependency if it won't be used by the app.
If L2 happens to be used by the app (through L1) while not declared as a dependency, there will be a NoClassDefFoundError.
If L2 is declared as a compile-time dependency of the app, and not used at runtime, it uselessly makes the jar larger and compile-time longer than needed.
Declaring L2 as a runtime dependency allows the JVM to lazy-load it, only when needed.