簡體   English   中英

程序可以在編譯期間依賴庫而不是運行時嗎?

[英]Can a program depend on a library during compilation but not runtime?

我了解運行時和編譯時之間的區別以及如何區分兩者,但我只是不認為需要區分編譯時和運行時依賴項。

我哽咽的是:程序如何在運行時不依賴它在編譯期間依賴的東西? If my Java app uses log4j, then it needs the log4j.jar file in order to compile (my code integrating with and invoking member methods from inside log4j) as well as runtime (my code has absolutely no control over what happens once code inside log4j .jar 運行)。

我正在閱讀 Ivy 和 Maven 等依賴關系解析工具,這些工具清楚地區分了這兩種類型的依賴關系。 我只是不明白它的必要性。

任何人都可以給出一個簡單的“國王英語”式的解釋,最好是一個即使像我這樣可憐的sap也能理解的實際例子?

運行時通常需要編譯時依賴項。 在 maven 中, compile范圍的依賴項將在運行時添加到類路徑中(例如,在戰爭中,它們將被復制到 WEB-INF/lib)。

但是,這不是嚴格要求的; 例如,我們可以針對某個 API 進行編譯,使其成為編譯時依賴項,但隨后在運行時包含一個還包括 API 的實現。

可能存在項目需要特定依賴項才能編譯但實際上並不需要相應代碼的邊緣情況,但這種情況很少見。

另一方面,包括在編譯時不需要的運行時依賴項是很常見的。 For instance, if you're writing a Java EE 6 application, you compile against the Java EE 6 API, but at runtime, any Java EE container can be used; 正是這個容器提供了實現。

使用反射可以避免編譯時依賴。 例如,可以使用 Class.forName 加載Class.forName驅動程序,並且加載的實際 class 可以通過配置文件進行配置。

每個 Maven 依賴項都有一個 scope 定義該依賴項在哪個類路徑上可用。

當您為項目創建 JAR 時,依賴項不會與生成的工件捆綁在一起; 它們僅用於編譯。 (However, you can still make maven include the dependencies in the built jar, see: Including dependencies in a jar with Maven )

當您使用 Maven 創建 WAR 或 EAR 文件時,您可以配置 Maven 以將依賴項與生成的工件捆綁在一起,您還可以將其配置為使用provided的 Z31A1ZEFD140BEA18BEFA8D 從 WAR 文件中排除某些依賴項

最常見的 scope — compile — 表示依賴項可用於您的項目在編譯類路徑、單元測試編譯和執行類路徑以及執行應用程序時的最終運行時類路徑。 在 Java EE web 應用程序中,這意味着將依賴項復制到您部署的應用程序中。 然而,在 JAR 文件中,使用compile scope 時將包含依賴項。

runtime scope 表示依賴項在單元測試執行和運行時執行類路徑上可用於您的項目,但與compile scope 不同,它在您編譯應用程序或其單元測試時不可用 運行時依賴項被復制到您部署的應用程序中,但在編譯期間不可用。 這有助於確保您不會錯誤地依賴特定的庫。 想象一下,您正在使用一個特定的日志記錄實現,但您只想在源代碼中導入一個日志記錄外觀。 您將包含具有runtime scope 的具體日志庫,因此您不會錯誤地依賴它。

最后, provided scope 表示您的應用程序在其中執行的容器代表您提供依賴關系。 在 Java EE 應用程序中,這意味着依賴項已經在 Servlet 容器或應用程序服務器的類路徑中,並且不會復制到您部署的應用程序中。 這也意味着你需要這個依賴來編譯你的項目。

您在編譯時需要在運行時可能需要的依賴項。 然而,許多庫在沒有所有可能的依賴項的情況下運行。 即一個庫,它可以使用四個不同的 XML 庫,但只需要一個即可工作。

許多庫,依次需要其他庫。 這些庫在編譯時不需要,但在運行時需要。 即當代碼實際運行時。

一般來說,您是對的,如果運行時和編譯時依賴項相同,這可能是理想的情況。

當這條規則不正確時,我會給你 2 個例子。

If class A depends on class B that depends on class C that depends on class D where A is your class and B, C and D are classes from different third party libraries you need only B and C at compile time and you need also D at運行。 通常程序使用動態 class 加載。 在這種情況下,您不需要在編譯時使用的庫動態加載的類。 此外,庫通常會選擇在運行時使用哪個實現。 例如 SLF4J 或 Commons Logging 可以在運行時更改目標日志實現。 在編譯時您只需要 SSL4J 本身。

相反的示例,當您在編譯時需要比在運行時更多的依賴項。 認為您正在開發必須在不同環境或操作系統下工作的應用程序。 您在編譯時需要所有特定於平台的庫,而在運行時只需要當前環境所需的庫。

我希望我的解釋有所幫助。

通常,static 依賴關系圖是動態依賴關系圖的子圖,請參見NDepend 作者的這篇博文

也就是說,有一些例外情況,主要是添加編譯器支持的依賴項,這些在運行時變得不可見。 例如,通過Lombok生成代碼或通過(可插入類型)Checker Framework進行附加檢查。

剛剛遇到一個可以回答您問題的問題。 servlet-api.jar是我的 web 項目中的臨時依賴項,在編譯時和運行時都需要。 但是servlet-api.jar也包含在我的 Tomcat 庫中。

The solution here is to make servlet-api.jar in maven available only at compile time and not packaged in my war file so that it would not clash with the servlet-api.jar contained in my Tomcat library.

我希望這能解釋編譯時和運行時依賴。

我了解運行時和編譯時之間的區別以及如何區分兩者,但我只是不認為需要區分編譯時和運行時依賴項。

一般的編譯時和運行時概念和 Maven 特定的compileruntime scope 依賴項是兩個非常不同的東西。 您不能直接比較它們,因為它們沒有相同的框架:一般的編譯和運行時概念是廣泛的,而 maven compileruntime scope 概念具體是關於根據時間的依賴關系可用性/可見性:編譯或執行。
Don't forget that Maven is above all a javac / java wrapper and that in Java you have a compile time classpath that you specify with javac -cp... and a runtime classpath that you specify with java -cp... .
It would be not wrong to consider the Maven compile scope as a way to add a dependency both in the Java compile and runtime classppath ( javac and java ) while the Maven runtime scope can be seen as a way to add a dependency only in the Java運行時類路徑 ( javac )。

我哽咽的是:程序如何在運行時不依賴它在編譯期間依賴的東西?

您描述的內容與runtime沒有任何關系並compile scope。
它看起來更像是provided的 scope,您指定一個依賴項在編譯時依賴於它,而不是在運行時。
您在需要編譯依賴項時使用它,但您不想將其包含在打包的組件(JAR、WAR 或任何其他組件)中,因為該依賴項已由環境提供:它可以包含在服務器或任何啟動指定為 Java 應用程序的類路徑的路徑。

If my Java app uses log4j, then it needs the log4j.jar file in order to compile (my code integrating with and invoking member methods from inside log4j) as well as runtime (my code has absolutely no control over what happens once code inside 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 scope 的常見用法是 JDBC 依賴聲明。 要編寫可移植代碼,您不希望客戶端代碼可能引用特定 DBMS 依賴項的類(例如:PostgreSQL JDBC 依賴項),但您希望將其包含在您的應用程序中,因為在運行時需要這些類來生成JDBC API 與此 DBMS 一起使用。

runtime scope 用於防止程序員在代碼中添加對實現庫的直接依賴項,而不是使用抽象或外觀。

換句話說,它強制使用接口。

具體例子:

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 文件等)時包含在內。

在編譯時,您啟用您的依賴項所期望的合同/api。 (例如:在這里您只需與寬帶互聯網提供商簽訂合同)在運行時實際上您正在使用依賴項。 (例如:這里您實際上正在使用寬帶互聯網)

要回答“程序如何在運行時不依賴於編譯期間依賴的東西?”這個問題,讓我們看一下注解處理器的示例。

假設您已經編寫了自己的注釋處理器,並且假設它在編譯時依賴於com.google.auto.service:auto-service以便它可以使用@AutoService 此依賴項僅在編譯注解處理器時需要,但在運行時不需要:所有其他依賴於您的注解處理器來處理注解的項目在運行時不需要com.google.auto.service:auto-service的依賴(也不是在編譯時,也不是在任何其他時間)。

這不是很常見,但它會發生。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM