120

我了解运行时和编译时之间的区别以及如何区分两者,但我只是不认为需要区分编译时和运行时依赖项。

我哽咽的是:程序如何在运行时不依赖它在编译期间依赖的东西?如果我的 Java 应用程序使用 log4j,那么它需要 log4j.jar 文件才能编译(我的代码与 log4j 内部的成员方法集成并调用成员方法)以及运行时(我的代码完全无法控制 log4j 内部的代码会发生什么.jar 运行)。

我正在阅读 Ivy 和 Maven 等依赖关系解析工具,这些工具清楚地区分了这两种类型的依赖关系。我只是不明白它的必要性。

任何人都可以给出一个简单的“国王英语”式的解释,最好是一个即使像我这样可怜的sap也能理解的实际例子?

4

10 回答 10

67

运行时通常需要编译时依赖项。在 maven 中,compile作用域的依赖将在运行时添加到类路径中(例如,在战争中,它们将被复制到 WEB-INF/lib)。

但是,这不是严格要求的;例如,我们可以针对某个 API 进行编译,使其成为编译时依赖项,但随后在运行时包含一个也包含该 API 的实现。

可能存在项目需要特定依赖项才能编译但实际上并不需要相应代码的边缘情况,但这种情况很少见。

另一方面,包括在编译时不需要的运行时依赖项是很常见的。例如,如果您正在编写 Java EE 6 应用程序,您可以针对 Java EE 6 API 进行编译,但在运行时,可以使用任何 Java EE 容器;正是这个容器提供了实现。

使用反射可以避免编译时依赖。例如,可以使用 a 加载 JDBC 驱动程序,并且加载Class.forName的实际类可以通过配置文件进行配置。

于 2011-08-15T20:49:03.360 回答
35

每个 Maven 依赖项都有一个范围,该范围定义了该依赖项在哪个类路径上可用。

当您为项目创建 JAR 时,依赖项不会与生成的工件捆绑在一起;它们仅用于编译。(但是,您仍然可以使 maven 在构建的 jar 中包含依赖项,请参阅:Include dependencies in a jar with Maven

当您使用 Maven 创建 WAR 或 EAR 文件时,您可以配置 Maven 以将依赖项与生成的工件捆绑在一起,您还可以将其配置为使用provided范围从 WAR 文件中排除某些依赖项。

最常见的范围 — compile— 表示您的项目在编译类路径、单元测试编译和执行类路径以及执行应用程序时的最终运行时类路径上都可以使用依赖项。在 Java EE Web 应用程序中,这意味着将依赖项复制到您部署的应用程序中。但是,在 JAR 文件中,使用范围时将包含依赖项。compile

runtimescope 表示依赖项在单元测试执行和运行时执行类路径上对您的项目可用,但与compile范围不同,当您编译应用程序或其单元测试时它不可用。运行时依赖项被复制到您部署的应用程序中,但在编译期间不可用。这有助于确保您不会错误地依赖特定的库。想象一下,您正在使用一个特定的日志记录实现,但您只想在源代码中导入一个日志记录外观。您将包含具有runtime范围的具体日志库,因此您不会错误地依赖它。

最后,provided范围表示您的应用程序在其中执行的容器代表您提供依赖关系。在 Java EE 应用程序中,这意味着依赖项已经在 Servlet 容器或应用程序服务器的类路径上,并且不会复制到您部署的应用程序中。这也意味着你需要这个依赖来编译你的项目。

于 2014-09-26T03:41:03.153 回答
9

您在编译时需要在运行时可能需要的依赖项。然而,许多库在没有所有可能的依赖项的情况下运行。ie 库可以使用四种不同的 XML 库,但只需要一个即可工作。

许多库,依次需要其他库。这些库在编译时不需要,但在运行时需要。即当代码实际运行时。

于 2011-08-15T20:50:41.430 回答
4

一般来说,您是对的,如果运行时和编译时依赖项相同,这可能是理想的情况。

当这条规则不正确时,我会给你 2 个例子。

如果 A 类依赖于 B 类,B 类依赖于 C 类,C 类依赖于 D 类,其中 A 是您的类,B、C 和 D 是来自不同第三方库的类,您在编译时只需要 B 和 C,您还需要 D运行。通常程序使用动态类加载。在这种情况下,您不需要在编译时使用的库动态加载的类。此外,库通常会选择在运行时使用哪个实现。例如 SLF4J 或 Commons Logging 可以在运行时更改目标日志实现。在编译时您只需要 SSL4J 本身。

相反的示例,当您在编译时需要比在运行时更多的依赖项。认为您正在开发必须在不同环境或操作系统下工作的应用程序。您在编译时需要所有特定于平台的库,而在运行时只需要当前环境所需的库。

我希望我的解释有所帮助。

于 2011-08-15T20:51:00.133 回答
3

通常,静态依赖关系图是动态依赖关系图的子图,例如参见NDepend 作者的这篇博文

也就是说,有一些例外情况,主要是添加编译器支持的依赖项,这些在运行时变得不可见。例如,通过Lombok生成代码或通过(可插入类型)Checker Framework进行附加检查。

于 2011-09-06T17:23:16.643 回答
3

我了解运行时和编译时之间的区别以及如何区分两者,但我只是不认为需要区分编译时和运行时依赖项。

一般的编译时和运行时概念以及 Maven 特定compileruntime范围依赖是两个非常不同的东西。您不能直接比较它们,因为它们没有相同的框架:一般的编译和运行时概念是广泛的,而 mavencompileruntime作用域的概念具体是关于根据时间的依赖关系可用性/可见性:编译或执行。
不要忘记 Maven 首先是一个javac/java包装器,而在 Java 中,您有一个用 指定的编译时类路径javac -cp ... 和一个用 指定的运行时类路径java -cp ...
将 Mavencompile范围视为在 Java 编译和运行时 classppath 中添加依赖项的一种方式并没有错(javacjava),而 Mavenruntime作用域可以看作是一种仅在 Java 运行时 classppath ( javac) 中添加依赖项的方法。

我哽咽的是:程序如何在运行时不依赖它在编译期间依赖的东西?

runtime您所描述的与范围没有任何关系compile。对于您为依赖项指定
的范围而言,它看起来更像是在编译时而不是在运行时依赖于该范围。 您使用它是因为您需要编译依赖项,但您不想将其包含在打包的组件(JAR、WAR 或任何其他组件)中,因为该依赖项已经环境提供:它可以包含在服务器或任何启动指定为 Java 应用程序的类路径的路径。 provided

如果我的 Java 应用程序使用 log4j,那么它需要 log4j.jar 文件才能编译(我的代码与 log4j 内部的成员方法集成并调用成员方法)以及运行时(我的代码完全无法控制 log4j 内部的代码会发生什么.jar 运行)。

在这种情况下是的。但是假设您需要在 log4j 前面编写一个依赖 slf4j 作为外观的可移植代码,以便以后能够切换到另一个日志记录实现(log4J 2、logback 或任何其他)。
在这种情况下,在您的 pom 中,您需要将 slf4j 指定为compile依赖项(这是默认值),但您将 log4j 依赖项指定为runtime依赖项:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
    <scope>runtime</scope>
</dependency>

这样,log4j 类无法在编译代码中引用,但您仍然可以引用 slf4j 类。
如果您随compile时间指定了这两个依赖项,则没有什么会阻止您在编译代码中引用 log4j 类,并且您可能会与日志记录实现产生不良耦合:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
</dependency>

作用域的一个常见用法runtime是 JDBC 依赖声明。要编写可移植代码,您不希望客户端代码可能引用特定 DBMS 依赖项的类(例如:PostgreSQL JDBC 依赖项),但您希望将其包含在应用程序中,因为在运行时需要这些类JDBC API 与这个 DBMS 一起工作。

于 2018-08-30T07:24:45.193 回答
2

刚刚遇到一个可以回答您问题的问题。servlet-api.jar是我的 Web 项目中的临时依赖项,在编译时和运行时都需要。但servlet-api.jar也包含在我的 Tomcat 库中。

这里的解决方案是使servlet-api.jarmaven 仅在编译时可用,而不是打包在我的 war 文件中,这样它就不会与servlet-api.jar我的 Tomcat 库中包含的内容发生冲突。

我希望这能解释编译时和运行时依赖。

于 2012-08-15T13:56:26.023 回答
2

范围是为了runtime防止程序员在代码中向实现库添加直接依赖项,而不是使用抽象或外观。

换句话说,它强制使用接口。

具体例子:

1)您的团队正在使用 SLF4J 而不是 Log4j。您希望您的程序员使用 SLF4J API,而不是 Log4j。Log4j 仅供 SLF4J 在内部使用。解决方案:

  • 将 SLF4J 定义为常规编译时依赖项
  • 将 log4j-core 和 log4j-api 定义为运行时依赖项。

2) 您的应用程序正在使用 JDBC 访问 MySQL。您希望您的程序员针对标准 JDBC 抽象进行编码,而不是直接针对 MySQL 驱动程序实现。

  • mysql-connector-java将(MySQL JDBC 驱动程序)定义为运行时依赖项。

运行时依赖项在编译期间被隐藏(如果您的代码对它们具有“直接”依赖关系,则会引发编译时错误),但在执行期间和创建可部署工件(WAR 文件、SHADED jar 文件等)时包含在内。

于 2020-01-29T10:13:46.997 回答
1

在编译时,您启用您的依赖项所期望的合同/api。(例如:在这里您只需与宽带互联网提供商签订合同)在运行时实际上您正在使用依赖项。(例如:这里您实际上正在使用宽带互联网)

于 2011-08-15T20:55:05.717 回答
0

要回答“程序如何在运行时不依赖于编译期间依赖的东西?”这个问题,让我们看一下注解处理器的示例。

假设您已经编写了自己的注解处理器,并且假设它具有编译时依赖项,com.google.auto.service:auto-service因此它可以使用@AutoService. 此依赖项仅在编译注解处理器时需要,但在运行时不需要:所有其他依赖于注解处理器来处理注解的项目在运行时不需要依赖com.google.auto.service:auto-service项(在编译时或任何其他时间都不需要) .

这不是很常见,但它会发生。

于 2018-03-31T09:32:54.540 回答