簡體   English   中英

Java:編譯器和JRE是否需要訪問所有第三方類文件?

[英]Java: Do BOTH the compiler AND the JRE require access to all 3rd-party class files?

我有15年的C ++經驗,但我不熟悉Java。 我試圖了解Java如何處理頭文件的缺失。 我有幾個與此問題相關的問題。

具體來說,假設我為導入第三方類'Z'(並使用Z)的類'A'編寫源代碼。 據我所知,在編譯時,Java編譯器必須“訪問”有關Z的信息才能編譯A.java,創建A.class。 因此,Z.java或Z.class(或包含其中一個的JAR;比如Z.jar)必須在編譯時出現在本地文件系統上 - 對嗎?

編譯器是否使用類加載器加載Z(重復 - 在編譯時)?

如果我在COMPILE時使用類加載器是正確的,那么如果需要用戶定義的類加載器(L)並且是正在編譯的項目的一部分呢? 例如,假設L負責通過網絡下載Z.class AT RUNTIME? 在這種情況下,Java編譯器將如何在編譯時獲取Z.class? 它會先嘗試編譯L,然后在編譯時使用L來獲取Z嗎?

據我所知,使用Maven構建項目,Z.jar可以在編譯時通過Internet定位在遠程存儲庫中 - 在ibiblio上,或在POM文件中定義的自定義存儲庫中。 我希望我是正確的,它是MAVEN負責在編譯時下載第三方JAR文件,而不是編譯器的JVM?

但請注意,在RUNTIME,A.class再次需要Z.class - JRE將如何知道從哪里下載Z.class(沒有Maven幫助)? 或者開發人員有責任將Z.class與A.class一起發布到應用程序(例如在JAR文件中)? (...假設未使​​用用戶定義的類加載器。)

現在一個相關的問題,只是為了確認:我假設一旦編譯,A.class只包含到Z.class的符號鏈接 - Z.class的字節碼不是A.class的一部分; 如果我錯了,請糾正我。 (在C ++中,靜態鏈接會將字節從Z.class復制到A.class中,而動態鏈接則不會。)

關於編譯過程的另一個相關問題:一旦描述Z的必要文件在編譯時位於CLASSPATH上,編譯器是否需要Z.class中的字節碼才能編譯A.java(如果需要,將構建Z.class) ,來自Z.java),還是Z.java足以滿足編譯器的需要?

我的整體困惑可歸納如下。 似乎Z的完整[byte]代碼需要存在TWICE - 一次在編譯期間,第二次在運行時 - 並且對於Java程序引用的所有類必須為true。 換句話說,每個類必須下載/呈現TWICE。 在編譯期間,不能將單個類表示為頭文件(因為它可以在C ++中)。

編譯器是否使用類加載器加載Z(重復 - 在編譯時)?

幾乎。 它使用JavaFileManager ,它在很多方面充當類加載器。 它實際上並沒有加載類,因為它需要從.java文件和.class文件創建類簽名。

我希望我是正確的,它是MAVEN負責在編譯時下載第三方JAR文件,而不是編譯器的JVM?

是的,雖然可以實現一個行為類似於URLClassLoader的JavaFileManager,但是Maven可以下載jar。 Maven管理jar的本地緩存,並根據需要從網絡填充緩存。

關於編譯過程的另一個相關問題:一旦描述Z的必要文件在編譯時位於CLASSPATH上,編譯器是否需要Z.class中的字節碼才能編譯A.java(如果需要,將構建Z.class) ,來自Z.java),還是Z.java足以滿足編譯器的需要?

它不需要所有字節碼。 只是類,方法和屬性簽名和元數據。 如果A依賴於Z,則可以通過在源路徑上找到的Z.java,在任何(類路徑,系統類路徑)上找到的Z.class或通過某些自定義擴展(如Z)來滿足該依賴關系。 JSP。

我的整體困惑可歸納如下。 似乎Z的完整[byte]代碼需要存在TWICE - 一次在編譯期間,第二次在運行時 - 並且對於Java程序引用的所有類必須為true。 換句話說,每個類必須下載/呈現TWICE。 在編譯期間,不能將單個類表示為頭文件(因為它可以在C ++中)。

也許一個例子可以幫助澄清這一點。 java語言規范要求編譯器進行某些優化。 內聯static final原始StringString

如果A類僅依賴於B作為常數:

class B {
  public static final String FOO = "foo";
}

class A {
  A() { System.out.println(B.FOO); }
}

然后可以在類路徑上編譯,加載和實例化A而不使用B.class 如果您更改並發送了具有不同FOO值的B.class ,則A仍將具有該編譯時依賴性。

因此,可以具有編譯時依賴性而不是鏈接時依賴性。

當然,通過反射可以獲得沒有編譯時依賴性的運行時依賴性。

總而言之,在編譯時,編譯器確保類訪問的方法和屬性可用。

在類加載時(運行時),字節碼驗證器檢查預期的方法和屬性是否真的存在。 因此,字節碼驗證器會仔細檢查編譯器所做的假設(內聯假設除外)。

可以模糊這些區別。 例如,JSP使用一個自定義類加載器來調用java編譯器,以便在運行時根據需要從源代碼編譯和加載類。

理解Maven如何適應圖片的最好方法是意識到它(大部分)沒有。

Maven不參與編譯器查找定義的過程,或者運行時系統加載類。 編譯器本身就是這樣做的...基於構建時類路徑所說的內容。 當您運行該應用程序時,Maven根本不再處於圖片中。

在構建時,Maven的作用是檢查在POM文件中聲明的項目依賴項,檢查版本,下載缺少的項目,將JAR放在一個眾所周知的位置,並為編譯器(和其他工具)創建一個“類路徑”。

然后,編譯器從這些JAR文件中“加載”它所需的類,以在已編譯的類文件中提取類型簽名信息。 它不使用常規類加載器來執行此操作,但用於查找類的基本算法是相同的。

編譯完成后,Maven會負責打包到JAR,WAR,EAR文件等,這些都是由POM文件指定的。 對於WAR或EAR文件,將所有必需的從屬JAR打包到文件中。

沒有Maven導向的JAR下載在運行時發生。 但是,運行應用程序可能涉及下載JAR文件; 例如,如果使用Java WebStart部署應用程序。 (但在這種情況下,JAR不會從Maven存儲庫下載...)

還有一些事情需要注意:

  • Maven根本不需要在圖片中。 您可以使用IDE來構建構建,Ant構建工具(可能是Ivy),Make甚至是“啞”shell腳本。 根據構建機制,您可能需要手動處理外部依賴項; 例如,找出外部JAR下載,放置它們等等。

  • Java運行時系統通常必須加載比編譯器更多的內容。 編譯器只需要加載那些類型檢查正在編譯的類所必需的類。

    例如,假設類A有一個使用類B作為參數的方法,而類B有一個使用類C作為參數的方法。 在編譯A ,需要加載B ,而不是C (除非A在某種程度上直接依賴於C )。 執行A ,需要加載BC

    第二個例子,假設A類依賴於接口I和實現IC1IC2 除非A明確依賴於IC1IC2 ,否則編譯器不需要加載它們來編譯A

  • 也可以在運行時動態加載類; 例如,通過調用Class.forName(className) ,其中className是一個字符串值表達式。


你寫了:

對於第二個要點中的示例 - 我認為開發人員可以選擇在編譯時為B提供一個不包含使用C的B方法的存根文件,並且A可以編譯得很好。 這將證實我的評估,在編譯時,在Java中完全允許所謂的“頭”文件,其中只聲明了必要的函數(即使是存根) - 所以這只是為了方便/約定,工具隨着時間的推移不再發展使用標頭/源文件區分。 (如我錯了請糾正我。)

它不是一個方便/進化的東西。 Java從未支持單獨的頭文件。 詹姆斯·戈斯林等人從頭文件和預處理器是一個壞主意的立場開始。

你假設的B版存根版必須擁有真實B所有可見方法,構造函數和字段,並且方法和構造函數必須具有實體。 存根B不會編譯。 (我猜在理論上,主體可能是空的,返回虛擬值或拋出未經檢查的異常。)

這種方法的問題在於它會非常脆弱 如果您在保持的存根和完整版本做出的最小的錯誤B步驟,其結果必然是類裝載器(在運行時)會報告一個致命的錯誤。

順便說一句,C和C ++幾乎是擁有單獨頭文件的例外。 在支持單獨編譯的大多數其他語言中(包括應用程序的不同文件),編譯器可以從實現源代碼中提取接口信息(例如,簽名)。

另外一個可以幫助的拼圖,接口和抽象類也被編譯成類文件。 因此,在編譯A時,理想情況下,您將針對API進行編譯,而不一定是具體類。 因此,如果A在編譯時使用接口B(由Z實現),則需要A和B的類文件,但在運行時,您需要A,B和Z的類文件。所有類都是動態鏈接的(正確的)可以破解文件並查看字節碼並查看其中的完全限定名稱。如果你很好奇, jclasslib是一個很好的檢查類文件和讀取字節碼的工具。 我可以在運行時替換類。 但是運行時的問題通常會導致各種形式的LinkageErrors

通常,如果一個類與您編譯的jar文件一起發布,則決定取決於您的特定方案。 假定在每個JRE實現中都有可用的類。 但是,如果我有自己的API和實現,我將不得不以某種方式提供它們運行的​​任何地方。 有一些API盡管例如servlet的 ,我會針對編譯該servlet API,但容器(例如的Websphere)負責提供該servlet API和實現在運行時,我(因此我不應該出貨我自己的這些副本)。

我有15年的C ++經驗,但我不熟悉Java。

您可能面臨的最大挑戰是許多在C ++中被視為重要的東西,例如sizeof()一個對象,無符號整數和析構函數,在Java中並不容易做到,並且沒有被視為具有相同的重要性和有其他解決方案/解決方案。

我試圖了解Java如何處理頭文件的缺失。 我有幾個與此問題相關的問題。

Java有一些概念與頭文件類似的接口,因為它們只包含沒有定義的聲明(和常量)。 類通常與該類的接口配對,有時一對一。

編譯器是否使用類加載器加載Z(重復 - 在編譯時)?

當類加載器加載一個類時,它會調用靜態初始化塊,它可以執行任何操作。 所有編譯器需要的是從類中提取元數據,而不是字節代碼,這就是它的作用。

是MAVEN負責在編譯時下載第三方JAR文件,而不是編譯器的JVM?

Maven必須將文件加載到本地文件系統,默認位置為~/.m2/repository

JRE如何知道從哪里下載Z.class(沒有Maven幫助)?

它必須要么使用Maven; 一些OSGi容器能夠動態加載和卸載不同的版本,例如,您可以在正在運行的系統中更改庫的版本,或從maven構建更新SNAPSHOT。

或者你有一個獨立的應用程序; 使用像appassembly這樣的Maven插件,您可以創建批處理/ shell腳本以及包含所需庫的副本的目錄。

或者是一個包含元信息和其中的許多jar的web存檔war (它只是一個裝有罐子的罐子;)

或者,開發人員有責任將Z.class與A.class一起發布到應用程序中

對於獨立應用程序是。

現在是一個相關的問題,僅供確認:我假設一旦編譯,A.class只包含指向Z.class的符號鏈接

從技術上講,它只包含帶有Z字符串,而不包含.class本身。 你可以改變很多Z而不再編譯A,它仍然可以工作。 例如,你可以編譯一次版本的Z並稍后用另一個版本替換它,應用程序仍然可以運行。 您甚至可以在應用程序運行時替換它。 ;)

Z.class的字節碼不是A.class的一部分;

編譯器旁邊沒有優化。 唯一重要的一個是IMHO,它是內聯編譯時常量。 這意味着如果在編譯A后更改Z中的常量,則在A中可能不會更改。(如果在編譯時使常量未知,則不會內聯它)

沒有內聯字節代碼,字節代碼中的本機代碼在運行時根據程序實際運行的方式進行內聯。 例如,假設您有一個具有N個實現的虛擬方法。 C ++編譯器不知道哪些內聯esp,因為它們在編譯時可能不可用。 但是,JVM可以看到哪些是最常用的(它在程序運行時收集統計信息)並且可以內聯兩個最常用的實現。 (關於在運行時刪除/更新其中一個類時會發生什么的思考的食物;)

如果我錯了,請糾正我。 (在C ++中,靜態鏈接會將字節從Z.class復制到A.class中,而動態鏈接則不會。)

Java只有動態鏈接,但這並不妨礙在運行時內聯代碼,這與使用宏一樣有效。

關於編譯過程的另一個相關問題:一旦描述Z的必要文件在編譯時位於CLASSPATH上,編譯器是否需要Z.class中的字節碼才能編譯A.java(如果需要,將構建Z.class) ,來自Z.java),還是Z.java足以滿足編譯器的需要?

編譯器將根據需要編譯所有.java文件。 您只需要提供.java但它必須編譯(即它的依賴項必須可用)但是如果您使用.class文件並不是所有的依賴都需要可用於編譯A.

我的整體困惑可歸納如下。 似乎Z的完整[byte]代碼需要存在TWICE - 一次在編譯期間,第二次在運行時 -

從技術上講,類包含字節碼和元數據,例如方法簽名,字段和常量。 在編譯時不使用字節代碼,只使用元信息。 編譯時的字節代碼不需要與運行時使用的字節代碼匹配。 (使用的簽名/字段可以)每個類的一個副本更簡單,但如果您出於某種目的需要,可以在編譯時使用精簡版本。

並且對於Java程序引用的所有類必須為true。 換句話說,每個類必須下載/呈現TWICE。 在編譯期間,不能將單個類表示為頭文件(因為它可以在C ++中)。

它只需要下載一次,因為它位於存儲庫中或某些磁盤上。 像標題這樣的接口可能就是你在編譯時需要的所有東西,它們可能是一個單獨的庫,但通常它並不是因為在大多數情況下只有一個存檔更簡單(OSGi是我知道它在哪里的唯一例子)值得分開他們)

你的摘要是正確的,但我想補充一點,如果你編譯成一個jar,那么jar將包含Z(如果Z是一個jar,只需要Z jar中需要的文件。

但是,相同的Z可用於編譯和運行時。

簡單地說,不。 如果你看一下說JDBC代碼,它是針對一個接口編譯的,為此目的就像一個頭文件,並使用反射在運行時引入正確的實現。 驅動程序根本不需要在構建機器上存在,盡管現在更簡潔的方法是通過依賴注入框架。

在任何情況下,沒有什么能阻止你編譯一個'header'類文件,然后針對實際的類文件運行(Java 主要是動態鏈接的),但這似乎為你自己做了額外的工作。

暫無
暫無

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

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