我理解运行时和编译时之间的区别以及如何区分这两者,但我只是没有看到需要区分编译时和运行时 dependencies .
我正在窒息的是:程序如何在编译期间不依赖运行时依赖的东西?如果我的Java应用程序使用log4j,那么它需要log4j.jar文件才能编译(我的代码集成并调用log4j内部的成员方法)以及运行时(我的代码完全无法控制log4j中的代码后发生的事情) .jar跑了) .
我正在阅读依赖解析工具,如Ivy和Maven,这些工具清楚地区分了这两种类型的依赖关系 . 我只是不明白它的必要性 .
任何人都可以给出一个简单的,“国王的英语”式的解释,最好有一个实际的例子,即使像我这样的可怜的人也能理解?
9 回答
刚遇到一个回答你问题的问题 .
servlet-api.jar
是我的Web项目中的瞬态依赖项,在编译时和运行时都需要 . 但servlet-api.jar
也包含在我的Tomcat库中 .这里的解决方案是使maven中的
servlet-api.jar
仅在编译时可用,而不是打包在我的war文件中,这样它就不会与我的Tomcat库中包含的servlet-api.jar
冲突 .我希望这解释了编译时间和运行时依赖性 .
通常,静态依赖关系图是动态关系图的子图,参见例如 . this blog entry from the author of NDepend .
也就是说,有一些例外,主要是添加编译器支持的依赖项,它们在运行时变得不可见 . 例如,通过Lombok进行代码生成或通过(pluggable type-)Checker Framework进行其他检查 .
运行时通常需要编译时依赖项 . 在maven中,一个
compile
范围的依赖项将在运行时添加到类路径中(例如,在战争中它们将被复制到WEB-INF / lib) .但是,并非严格要求;例如,我们可以针对某个API进行编译,使其成为编译时依赖项,但是在运行时包含一个也包含API的实现 .
可能存在边缘情况,其中项目需要一定的依赖性来编译,但实际上并不需要相应的代码,但这些很少见 .
另一方面,包括编译时不需要的运行时依赖性是非常常见的 . 例如,如果您正在编写Java EE 6应用程序,则可以针对Java EE 6 API进行编译,但在运行时,可以使用任何Java EE容器;这个容器提供了实现 .
使用反射可以避免编译时依赖性 . 例如,可以使用Class.forName加载JDBC驱动程序,并且可以通过配置文件配置加载的实际类 .
每个Maven依赖项都有一个范围,用于定义依赖项可用的类路径 .
为项目创建JAR时,依赖项不会与生成的工件捆绑在一起;它们仅用于编译 . (但是,您仍然可以在构建的jar中使maven包含依赖项,请参阅:Including dependencies in a jar with Maven)
使用Maven创建WAR或EAR文件时,可以将Maven配置为将依赖项与生成的工件捆绑在一起,还可以将其配置为使用提供的范围从WAR文件中排除某些依赖项 .
最常见的范围 - Compile Scope - 表示在执行应用程序时,依赖项可用于编译类路径上的项目,单元测试编译和执行类路径以及最终的运行时类路径 . 在Java EE Web应用程序中,这意味着将依赖项复制到已部署的应用程序中 . 但是在.jar文件中,编译范围不包含依赖项 .
Runtime Scope 表示您的项目在单元测试执行和运行时执行类路径上可以使用依赖项,但与编译范围不同,它是 is not available when you compile your application 或其单元测试 . A Runtime Dependency is copied into your deployed application, but it is not available during compilation! 这有助于确保您不会错误地依赖特定的库 . (参见例如:http://www.tugay.biz/2016/12/apache-commons-logging-log4j-maven.html)
最后, Provided Scope 表示应用程序执行的容器代表您提供依赖关系 . 在Java EE应用程序中,这意味着依赖关系已经存在于Servlet容器或应用程序服务器的类路径中,并且 is not copied into your deployed application. 这也意味着您需要此依赖关系来编译项目 .
您需要在运行时可能需要的编译时依赖项 . 但是,许多库运行时没有所有可能的依赖项 . 即一个可以使用四个不同XML库但只需要一个工作的库 .
许多库依次需要其他库 . 编译时不需要这些库,但在运行时需要这些库 . 即当代码实际运行时 .
通常,您是正确的,如果运行时和编译时依赖性相同,则它是理想的情况 .
当这条规则不正确时,我会举两个例子 .
如果A类依赖于依赖于C类的B类,它取决于D类,其中A是您的类,B,C和D是来自不同第三方库的类,您只需要B和C在编译时,您还需要在运行时使用D.程序通常使用动态类加载 . 在这种情况下,您不需要在编译时使用的库动态加载的类 . 此外,库通常会选择在运行时使用哪个实现 . 例如,SLF4J或Commons Logging可以在运行时更改目标日志实现 . 编译时只需要SSL4J .
相反,在编译时需要比运行时更多的依赖项 . 认为您正在开发必须在不同环境或操作系统上工作的应用程序 . 在编译时需要所有特定于平台的库,并且在运行时只需要当前环境所需的库 .
我希望我的解释有所帮助 .
一般的编译时和运行时概念以及Maven特定的
compile
和runtime
范围依赖关系是两个非常不同的东西 . 您不能直接比较它们,因为它们没有相同的框架:一般的编译和运行时概念是广泛的,而mavencompile
和runtime
范围概念是根据时间特定的依赖关系可用性/可见性:编译或执行 .不要忘记,Maven首先是
javac
/java
包装器,而在Java中,您有一个使用javac -cp ...
指定的编译时类路径以及使用java -cp ...
指定的运行时类路径 .将Maven
compile
范围视为在Java编译和运行时classppath(javac
和java
)中添加依赖关系的方法并不错,而Mavenruntime
范围可以被视为仅在Java中添加依赖关系的方法运行时classppath(javac
) .您描述的内容与
runtime
和compile
范围没有任何关系 .对于依赖项在编译时但不在运行时依赖于它的
provided
范围看起来更像 .您可以使用它,因为您需要编译依赖项,但您不希望将它包含在打包的组件(JAR,WAR或任何其他组件)中,因为依赖项已由环境提供:它可以包含在服务器中或任何其中启动指定为Java应用程序的类路径的路径 .
在这种情况下是的 . 但是假设您需要编写一个可移植代码,该代码依赖于slf4j作为log4j前面的外观,以便以后能够切换到另一个日志记录实现(log4J 2,logback或任何其他) .
在这种情况下,您需要将slf4j指定为
compile
依赖项(它是默认值),但您将log4j依赖项指定为runtime
依赖项:这样,在编译的代码中无法引用log4j类,但您仍然可以引用slf4j类 .
如果您使用
compile
时间指定了两个依赖项,则没有任何内容可以阻止您在已编译的代码中引用log4j类,并且您可以创建与日志记录实现的不合需要的耦合:runtime
scope的常见用法是JDBC依赖性声明 . 要编写可移植代码,您不需要客户端代码可能引用特定DBMS依赖项的类(例如:PostgreSQL JDBC依赖项),但是您希望所有相同的内容都包含在应用程序中,因为在运行时需要使用JDBC来使JDBC API与此DBMS一起使用 .在编译时,您可以从依赖项中启用期望的contract / api . (例如:在这里,您只需与宽带互联网提供商签订 Contract )在运行时实际上您正在使用依赖项 . (例如:你实际上在这里使用宽带互联网)
要回答“程序如何在运行时不依赖于编译期间依赖于某些东西?”的问题,让我们看一下注释处理器的示例 .
假设您已编写自己的注释处理器,并假设它对
com.google.auto.service:auto-service
具有编译时依赖性,以便它可以使用@AutoService
. 这种依赖关系只需要编译注释处理器,但在运行时不需要:所有其他项目(取决于注释处理器用于处理注释)不需要在运行时依赖于com.google.auto.service:auto-service
(也不需要在编译时或任何其他时间) ) .这不常见,但它发生了 .