程序在编译过程中可以依赖库,但运行时不依赖库吗?


110

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

我正在cho的是:程序如何在运行时不依赖于编译期间依赖的东西?如果我的Java应用程序使用log4j,则它需要log4j.jar文件才能进行编译(我的代码与log4j集成并从其中调用成员方法)以及运行时(我的代码绝对无法控制log4j内的代码一旦发生了什么.jar已运行)。

我正在阅读诸如Ivy和Maven之类的依赖项解析工具,这些工具显然可以区分这两种类型的依赖项。我只是不明白它的需要。

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


2
您可以使用反射,并使用编译时不可用的类。认为“插件”。
Per Alexandersson

Answers:


64

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

但是,并非严格要求;例如,我们可以针对某个API进行编译,使其具有编译时依赖性,但随后在运行时包括一个也包含API的实现。

在某些情况下,项目需要一定的依赖性进行编译,但实际上并不需要相应的代码,但是这种情况很少见。

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

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


17
关于Java EE API,这不是“提供的”依赖范围的目的吗?
凯文

15
lombok(www.projectlombok.org)是一个需要编译但在运行时不需要依赖的示例。该jar用于在编译时转换Java代码,但在运行时完全不需要。指定范围“已提供”将导致jar不包含在war / jar中。
凯文

2
@Kevin是的,很好,该provided范围添加了编译时依赖项,而没有添加运行时依赖项,因为期望该依赖项将在运行时通过其他方式(例如,容器中的共享库)提供。runtime另一方面,添加了运行时相关性,而没有使其成为编译时相关性。
Artefacto

因此可以肯定地说,“模块配置”(使用Ivy术语)与项目根目录下的主目录之间通常存在1:1的关联?例如,我所有依赖JUnit JAR的JUnit测试都将位于test /根目录下,等等。我只是看不到如何将配置在相同源根目录下的相同类“配置”为依赖于不同在任何给定时间的JAR。如果需要log4j,则需要log4j。没有办法告诉相同的代码在1配置下调用log4j调用,但是在某些“非日志记录”配置下忽略log4j调用,对吗?
IAmYourFaja 2011年

30

每个Maven依赖项都有一个范围,该范围定义了依赖项所在的类路径。

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

使用Maven创建WAR或EAR文件时,可以将Maven配置为将依赖项与生成的工件捆绑在一起,也可以使用提供的作用域将其配置为从WAR文件中排除某些依赖项。

最常见的作用域- 编译作用域 -表示在您执行应用程序时,项目对编译类路径,单元测试编译和执行类路径以及最终的运行时类路径具有依赖性。在Java EE Web应用程序中,这意味着将依赖项复制到已部署的应用程序中。但是,在.jar文件中,依赖项将不包含在编译范围内。

Runtime Scope表示依赖项对您的项目在单元测试执行和运行时执行类路径上可用,但是与编译范围不同,在您编译应用程序或其单元测试,依赖项不可用运行时依赖关系已复制到已部署的应用程序中,但是在编译期间不可用!这有助于确保您不会错误地依赖特定的库。

最后,Provided Scope指示您的应用程序在其中执行的容器代表您提供了依赖性。在Java EE应用程序中,这意味着依赖项已经在Servlet容器或应用程序服务器的类路径上,而没有复制到已部署的应用程序中。这也意味着您需要此依赖项来编译项目。


@Koray Tugay答案更精确:)我有一个简短的问题说我有一个依赖罐子,带有​​运行时间范围。行家会在编译时寻找jar吗?
gks

@gks不,在编译时不需要它。
Koray Tugay

9

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

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


您能否给我们提供这样的库的示例,这些示例在编译时将不需要,而在运行时将需要?
Cristiano 2014年

1
@Cristiano所有的JDBC库都是这样的。也是实现标准API的库。
彼得·劳瑞

4

通常来说,如果运行时和编译时的依赖关系相同,那么这是理想的情况。

当这个规则不正确时,我将举两个例子。

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

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

希望我的解释有所帮助。


您能否在示例中详细说明为什么在编译时需要C?我得到的印象是(来自stackoverflow.com/a/7257518/6095334),在编译时是否需要C取决于A所引用的方法和字段(来自B)。
Hervian


2

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

此处的解决方案是使servlet-api.jarmaven仅在编译时可用,并且不打包在我的war文件中,以便它不会与servlet-api.jarTomcat库中包含的内容冲突。

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


3
您的示例对于给定的问题实际上是不正确的,因为它解释了compileprovided作用域之间的区别,而不是compile和之间的区别runtimeCompile scope在编译时都需要,并且打包在您的应用中。Provided scope仅在编译时需要,而未打包在您的应用中,因为它是通过其他方式提供的,例如它已经在Tomcat服务器中。
2013年

1
嗯,我认为这是一个相当不错的例子,因为这个问题是关于编译时和运行时的依赖性,而不是有关compileruntime Maven的范围。的provided范围是Maven的把手,其中一个编译时间依赖性不应该被包括在运行时包的情况下的方式。
Christian Gawron '16

1

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

通用的编译时和运行时概念以及Maven特定compileruntime作用域依赖性是两件截然不同的东西。您不能直接比较它们,因为它们没有相同的框架:一般的编译和运行时概念很宽泛,而Maven compileruntime范围的概念则是根据时间(编译或执行)专门确定依赖项的可用性/可见性。
别忘了Maven首先是javac/ java包装器,在Java中,您有一个指定的编译时类路径javac -cp ... 和一个您指定的运行时类路径java -cp ...
将Maven compile范围视为在Java编译和运行时classppath中添加依赖项的一种方式是正确的(javacjava),而Maven runtime范围可以看作是仅在Java运行时classppath(javac)中添加依赖项的一种方式。

我正在cho的是:程序如何在运行时不依赖于编译期间依赖的东西?

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

如果我的Java应用程序使用log4j,则它需要log4j.jar文件以便进行编译(我的代码与log4j集成并从其中调用成员方法)以及运行时(我的代码绝对无法控制log4j内的代码一旦发生了什么.jar已运行)。

在这种情况下,是的。但是,假设您需要编写一个依赖slf4j的可移植代码,将其作为log4j前面的外观,以便以后可以切换到另一个日志记录实现(log4J 2,logback或其他任何方法)。
在这种情况下,您需要将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一起使用。


0

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


0

为了回答“程序在运行时如何不依赖于某个东西的问题?”这一问题,让我们看一下注释处理器的示例。

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

这不是很常见,但是确实发生了。


0

runtime作用域是为了防止程序员将直接依赖项添加到代码中的实现库,而不是使用抽象或外观。

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

具体示例:

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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.