Java的面向?qū)ο缶幊?/a>">Java的面向?qū)ο缶幊?/a>
868
2025-04-01
做一個(gè)終身學(xué)習(xí)的人。
Java 9
在本章,主要介紹以下內(nèi)容:
新的JDK版本控制方案是什么
如何使用Runtime.Version類解析JDK版本字符串
JDK JRE 9的新目錄布局是什么
JDK 9中的批注的標(biāo)準(zhǔn)覆蓋機(jī)制如何工作的
在JDK 9中使用擴(kuò)展機(jī)制的變化
JDK 9中的類加載器如何工作以及模塊的加載方式
資源如何封裝在JDK 9中的模塊中
如何使用Module,Class和ClassLoader類中的資源查找方法訪問模塊中的資源
jrt URL方案是什么,以及如何使用它來訪問運(yùn)行時(shí)映像中的資源
如何訪問JDK 9中的JDK內(nèi)部API以及JDK 9中已刪除的JDK API列表
JDK 9中如何使用--patch-module命令行選項(xiàng)替換模塊中的類和資源
一. 新的JDK版本控制方案
在JDK 9之前,JDK版本控制方案對開發(fā)人員來說并不直觀,程序解析并不容易。 看看這兩個(gè)JDK版本,你不能說出他們之間的微妙差異。 很難回答一個(gè)簡單的問題:哪個(gè)版本包含最新的安全修復(fù)程序,JDK 7 Update 55或JDK 7 Update 60? 答案不是很明顯的,你可能已經(jīng)猜到了JDK 7 Update 60。這兩個(gè)版本都包含相同的安全修復(fù)程序。 JDK 8 Update 66,1.8.0_66和JDK 8u66版本有什么區(qū)別? 它們代表相同的版本。 在了解版本字符串中包含的詳細(xì)信息之前,有必要詳細(xì)了解版本控制方案。 JDK 9試圖規(guī)范JDK版本控制方案,因此人們很容易理解,易于程序解析,并遵循行業(yè)標(biāo)準(zhǔn)版本控制方案。
JDK 9包含一個(gè)名為Runtime.Version的靜態(tài)嵌套類,它表示Java SE平臺實(shí)現(xiàn)的版本字符串。 它可以用于表示,解析,驗(yàn)證和比較版本字符串。
版本字符串按順序由以下四個(gè)元素組成。 只有第一個(gè)是強(qiáng)制性的:
版本號
預(yù)發(fā)行信息
構(gòu)建信息
附加信息
以下正則表達(dá)式定義版本字符串的格式:
$vnum(-$pre)?(\+($build)?(-$opt)?)?
一個(gè)簡短版本的字符串由一個(gè)版本號碼組成,可選地包含預(yù)發(fā)布信息:
$vnum(-$pre)?
可以使用只包含主版本號“9”的版本字符串。“9.0.1-ea + 154-20170130.07.36am”,包含版本字符串的所有部分。
1. 版本號
版本號是按句點(diǎn)分隔的元素序列。 它可以是任意長度。 其格式如下:
^[1-9][0-9]*(((\.0)*\.[1-9][0-9]*)*)*$
版本號可以由一到四個(gè)元素組成,如下所示:
$major.$minor.$security(.$addtionalInfo)
$major元素代表JDK版本的主要版本。 主要版本是遞增的,其中包含重要的新功能。 例如,JDK 8的主要版本為8,對于JDK 9為9。當(dāng)主版本號增加時(shí),版本號中的所有其他部分都將被刪除。 例如,如果版本號為9.2.2.1,則主版本號從9增加到10時(shí),新版本號將為10。
$minor元素代表JDK版本的次要版本。 增加一個(gè)小的更新版本,例如錯(cuò)誤修復(fù),新的垃圾收集器,新的JDK特定的API等。
$security元素表示JDK版本的安全級別更新。 它會(huì)增加一個(gè)安全更新。 當(dāng)次要版本號增加時(shí),該元素不會(huì)重置。 給定$major的$security更高值總是表示更安全的版本。 例如,JDK版本9.1.7與JDK版本9.5.7一樣安全,因?yàn)閮蓚€(gè)版本的安全級別是相同的,也就是7。另一個(gè)例子,JDK版本9.2.2比9.2.1更安全,因?yàn)閷τ谙嗤闹饕姹?,前者的安全級別為2大于后者的安全級別1。
以下規(guī)則適用于版本號:
所有元素必須是非負(fù)整數(shù)。
前三個(gè)要素分別被視為主要版本,次要版本和安全級別;其余的(如果存在)被視為附加信息,例如指示補(bǔ)丁發(fā)布的數(shù)字。
只有主要版本元素是強(qiáng)制性的。
版本號的元素不能包含前導(dǎo)零。 例如,JDK 9的主要版本是9,而不是09。
后面的元素不能為零。 也就是說,版本號不能為9.0.0。 它可以是9,9.2或9.0.x,其中x是正整數(shù)。
2. 預(yù)發(fā)行信息
版本字符串中的$pre元素是預(yù)發(fā)行標(biāo)識符,例如早期訪問版本的ea,預(yù)發(fā)行版快照,以及開發(fā)人員內(nèi)部構(gòu)建版本。 這是可選的。 如果它存在,它以前綴為連字符( - ),并且必須是與正則表達(dá)式([a-zA-Z0-9] +)匹配的字母數(shù)字字符串)。 以下版本字符串包含9作為版本號,ea作為預(yù)發(fā)布信息。
9-ea
3. 構(gòu)建信息
版本字符串中的$build元素是為每個(gè)提升的構(gòu)建增加的構(gòu)建號。 這是可選的。當(dāng)版本號的任何部分增加時(shí),它將重置為1。 如果它存在,它加上加號(+),并且必須匹配正則表達(dá)式(0 | [1-9] [0-9] *)。 以下版本的字符串包含154作為版本號。
9-EA+154
4. 附加信息
版本字符串中的$opt元素包含其他構(gòu)建信息,例如內(nèi)部構(gòu)建的日期和時(shí)間。這是可選的。它是字母和數(shù)字,可以包含連字符和句點(diǎn)。 如果它存在,它以前綴為連字符(-),并且必須與正則表達(dá)式([-a-zA-Z0-9 \。] +)匹配。 如果$build不存在,則需要在$opt值前加一個(gè)加號,后跟連字符(+ -)來指定$opt的值。 例如,在9-ea+132-2016-08-23中,$build為132,$opt為2016-08-23; 在9+-123中,$pre和$build缺失,$opt為123。以下版本字符串在其附加信息元素中加入發(fā)布的日期和時(shí)間:
9-EA+154-20170130.07.36am
5. 解析舊版和新版字符串
JDK版本或者是受限更新版本,其中包括新功能和非安全修補(bǔ)程序,或重要補(bǔ)丁更新,其中僅包括針對安全漏洞的修補(bǔ)程序。 版本字符串包括版本號,包括更新號和構(gòu)建號。 限制更新版本的編號為20的倍數(shù)。重要補(bǔ)丁更新使用奇數(shù),通過將五加倍加到先前的限制更新中,并在需要時(shí)添加一個(gè)以保持計(jì)算結(jié)果為奇數(shù)。 一個(gè)例子是1.8.0_31-b13,它是JDK主版本8的更新31。 它的內(nèi)部版本號是13。注意,在JDK 9之前,版本字符串始終以1開頭。
Tips
解析版本字符串以獲取JDK版本的主版本的現(xiàn)有代碼可能會(huì)在JDK 9中失敗,具體取決于其使用的邏輯。 例如,如果邏輯通過跳過第一個(gè)元素(以前為1)來查找第二個(gè)元素的主版本,邏輯將失敗。 例如,如果它從1.8.0返回8,那么它將從9.0.1返回0,在那里你會(huì)期望9。
6. 系統(tǒng)屬性的版本更改
在JDK 9中,包含JDK版本字符串的系統(tǒng)屬性返回的值已更改。 下面表格是這些系統(tǒng)屬性及其格式的列表。 $vstr,$vnum和$pre分別指版本字符串,版本號和預(yù)發(fā)布信息。
7. 使用Runtime.Version類
DK 9添加了一個(gè)名為Runtime.Version的靜態(tài)嵌套類,其實(shí)例代表版本字符串。 Version類沒有公共構(gòu)造函數(shù)。 獲取其實(shí)例的唯一方法是調(diào)用靜態(tài)方法parse(String vstr)。 如果版本字符串為空或無效,該方法可能會(huì)拋出運(yùn)行時(shí)異常。
import java.lang.Runtime.Version;
...
// Parse a version string "9.0.1-ea+132"
Version version = Version.parse("9.0.1-ea+132");
Runtime.Version類中的以下方法返回版本字符串的元素。 方法名稱足夠直觀,可以猜測它們返回的元素值的類型。
int major()
int minor()
int security()
Optional
Optional
Optional
注意,對于可選元素,$pre,$build和$opt,返回類型為Optional。 對于可選的$minor和$security元素,返回類型為int,而不是Optional,如果版本字符串中缺少$minor和$security,則返回零。
回想一下,版本字符串中的版本號可能包含第三個(gè)元素之后的附加信息。 Version類不包含直接獲取附加信息的方法。 它包含一個(gè)version()方法,該方法返回List
Runtime.Version類包含在次序和等式方面比較兩個(gè)版本字符串的方法。 可以比較它們或者不包含可選的構(gòu)建信息($opt)。 這些比較方法如下:
int compareTo(Version v)
int compareToIgnoreOptional(Version v)
boolean equals(Object v)
boolean equalsIgnoreOptional(Object v)
如果v1小于等于或大于v2,表達(dá)式v1.compareTo(v2)將返回負(fù)整數(shù),零或正整數(shù)。 compareToIgnoreOptional()方法的工作方式與compareTo()方法相同,只不過它在比較時(shí)忽略了可選的構(gòu)建信息。 equals()和equalsIgnoreOptional()方法將兩個(gè)版本字符串進(jìn)行比較,不包含可選構(gòu)建信息。
哪個(gè)版本的字符串代表最新版本:9.1.1或9.1.1-ea? 第一個(gè)不包含預(yù)發(fā)行元素,而第二個(gè)字符串包含,所以第一個(gè)是最新版本。 哪個(gè)版本的字符串代表最新版本:9.1.1或9.1.1.1-ea? 這一次,第二個(gè)代表最新的版本。 比較發(fā)生在序列$vnum,$pre,$build和$opt。 當(dāng)版本號較大時(shí),不比較版本字符串中的其他元素。
此部分的源代碼位于名為com.jdojo.version.string的模塊中,其聲明如下所示。
// module-info.java
module com.jdojo.version.string {
exports com.jdojo.version.string;
}
下面代碼包含一個(gè)完整的程序,顯示如何使用Runtime.Version類來提取版本字符串的所有部分。
com.jdojo.version.string
// VersionTest.java
package com.jdojo.version.string;
import java.util.List;
import java.lang.Runtime.Version;
public class VersionTest {
public static void main(String[] args) {
String[] versionStrings = {
"9", "9.1", "9.1.2", "9.1.2.3.4", "9.0.0",
"9.1.2-ea+153", "9+132", "9-ea+132-2016-08-23", "9+-123",
"9.0.1-ea+132-2016-08-22.10.56.45am"};
for (String versonString : versionStrings) {
try {
Version version = Version.parse(versonString);
// Get the additional version number elements
// which start at 4th element
String vnumAdditionalInfo = getAdditionalVersionInfo(version);
System.out.printf("Version String=%s%n", versonString);
System.out.printf("Major=%d, Minor=%d, Security=%d, Additional Version=%s,"
+ " Pre=%s, Build=%s, Optional=%s %n%n",
version.major(),
version.minor(),
version.security(),
vnumAdditionalInfo,
version.pre().orElse(""),
version.build().isPresent() ? version.build().get().toString() : "",
version.optional().orElse(""));
} catch (Exception e) {
System.out.printf("%s%n%n", e.getMessage());
}
}
}
// Returns the version number elements from the 4th elements to the end
public static String getAdditionalVersionInfo(Version v) {
String str = "";
List
int size = vnum.size();
if (size >= 4) {
str = str + String.valueOf(vnum.get(3));
}
for (int i = 4; i < size; i++) {
str = str + "." + String.valueOf(vnum.get(i));
}
return str;
}
}
VersionTest類,顯示如何使用Runtime.Version類來處理版本字符串。
下面是輸出結(jié)果:
Version String=9
Major=9, Minor=0, Security=0, Additional Version=, Pre=, Build=, Optional=
Version String=9.1
Major=9, Minor=1, Security=0, Additional Version=, Pre=, Build=, Optional=
Version String=9.1.2
Major=9, Minor=1, Security=2, Additional Version=, Pre=, Build=, Optional=
Version String=9.1.2.3.4
Major=9, Minor=1, Security=2, Additional Version=3.4, Pre=, Build=, Optional=
Invalid version string: '9.0.0'
Version String=9.1.2-ea+153
Major=9, Minor=1, Security=2, Additional Version=, Pre=ea, Build=153, Optional=
Version String=9+132
Major=9, Minor=0, Security=0, Additional Version=, Pre=, Build=132, Optional=
Version String=9-ea+132-2016-08-23
Major=9, Minor=0, Security=0, Additional Version=, Pre=ea, Build=132, Optional=2016-08-23
Version String=9+-123
Major=9, Minor=0, Security=0, Additional Version=, Pre=, Build=, Optional=123
Version String=9.0.1-ea+132-2016-08-22.10.56.45am
Major=9, Minor=0, Security=1, Additional Version=, Pre=ea, Build=132, Optional=2016-08-22.10.56.45am
二. JDK和JRE的改變
JDK和JRE已經(jīng)在Java SE 9中進(jìn)行了模塊化處理。對結(jié)構(gòu)進(jìn)行了一些修改。 還進(jìn)行了一些其他更改,以提高性能,安全性和可維護(hù)性。 大多數(shù)這些變化會(huì)影響類庫開發(fā)人員和IDE開發(fā)人員,而不是應(yīng)用程序開發(fā)人員。為了討論這些變化,把它們分為三大類:
布局變化
行為變化
API更改
以下部分將詳細(xì)介紹這些改變。
1. JDK和JRE的布局變化
結(jié)構(gòu)更改會(huì)影響運(yùn)行時(shí)映像中的目錄和文件的組織方式,并影響其內(nèi)容。 在Java SE 9之前,JDK構(gòu)建系統(tǒng)用于生成兩種類型的運(yùn)行時(shí)映像 ——Java運(yùn)行時(shí)環(huán)境(JRE)和Java開發(fā)工具包(JDK)。 JRE是Java SE平臺的完整實(shí)現(xiàn),JDK包含了JRE和開發(fā)工具和類庫。 可下圖顯示了Java SE 9之前的JDK安裝中的主目錄。JDK_HOME是安裝JDK的目錄。 如果你只安裝了JRE,那么你只有在jre目錄下的目錄。
Java SE 9之前的JDK和JRE目錄布局
在 Java SE 9之前,JDK中:
bin目錄用于包含命令行開發(fā)和調(diào)試工具,如javac,jar和javadoc。 它還用于包含Java命令來啟動(dòng)Java應(yīng)用程序。
include目錄包含在編譯本地代碼時(shí)使用的C/C++頭文件。
lib目錄包含JDK工具的幾個(gè)JAR和其他類型的文件。 它有一個(gè)tools.jar文件,其中包含javac編譯器的Java類。
jre\bin目錄包含基本命令,如java命令。 在Windows平臺上,它包含系統(tǒng)的運(yùn)行時(shí)動(dòng)態(tài)鏈接庫(DLL)。
jre\lib目錄包含用戶可編輯的配置文件,如.properties和.policy文件。
jre\lib\approved目錄包含允許使用標(biāo)準(zhǔn)覆蓋機(jī)制的JAR。 這允許在Java社區(qū)進(jìn)程之外創(chuàng)建的實(shí)施標(biāo)準(zhǔn)或獨(dú)立技術(shù)的類和接口的更高版本被并入Java平臺。 這些JAR被添加到JVM的引導(dǎo)類路徑中,從而覆蓋了Java運(yùn)行時(shí)中存在的這些類和接口的任何定義。
jre\lib\ext目錄包含允許擴(kuò)展機(jī)制的JAR。 該機(jī)制通過擴(kuò)展類加載器(該類加載器)加載了該目錄中的所有JAR,該引導(dǎo)類加載器是系統(tǒng)類加載器的子進(jìn)程,它加載所有應(yīng)用程序類。 通過將JAR放在此目錄中,可以擴(kuò)展Java SE平臺。 這些JAR的內(nèi)容對于在此運(yùn)行時(shí)映像上編譯或運(yùn)行的所有應(yīng)用程序都可見。
jre\lib目錄包含幾個(gè)JAR。 rt.jar文件包含運(yùn)行時(shí)的Java類和資源文件。 許多工具依賴于rt.jar文件的位置。
jre\lib目錄包含用于非Windows平臺的動(dòng)態(tài)鏈接本地庫。
jre\lib目錄包含幾個(gè)其他子目錄,其中包含運(yùn)行時(shí)文件,如字體和圖像。
JDK和JRE的根目錄包含多個(gè)文件,如COPYRIGHT,LICENSE和README.html。 根目錄中的發(fā)行文件包含一個(gè)描述運(yùn)行時(shí)映像(如Java版本,操作系統(tǒng)版本和體系結(jié)構(gòu))的鍵值對。 以下代碼顯示了JDK 8中的示例版本文件的部分內(nèi)容:
JAVA_VERSION="1.8.0_66"
OS_NAME="Windows"
OS_VERSION="5.2"
OS_ARCH="amd64"
BUILD_TYPE="commercial"
Java SE 9調(diào)整了JDK的目錄層次結(jié)構(gòu),并刪除了JDK和JRE之間的區(qū)別。 下圖顯示了Java SE 9中JDK安裝的目錄。JDK 9中的JRE安裝不包含include和jmods目錄。
Java SE 9中的JDK目錄布局
在Java SE 9 的JDK中:
沒有名為jre的子目錄。
bin目錄包含所有命令。 在Windows平臺上,它繼續(xù)包含系統(tǒng)的運(yùn)行時(shí)動(dòng)態(tài)鏈接庫。
conf目錄包含用戶可編輯的配置文件,例如以前位于jre\lib目錄中的.properties和.policy文件。
include目錄包含要在以前編譯本地代碼時(shí)使用的C/C++頭文件。 它只存在于JDK中。
jmods目錄包含JMOD格式的平臺模塊。 創(chuàng)建自定義運(yùn)行時(shí)映像時(shí)需要它。 它只存在于JDK中。
legal 目錄包含法律聲明。
lib目錄包含非Windows平臺上的動(dòng)態(tài)鏈接本地庫。 其子目錄和文件不應(yīng)由開發(fā)人員直接編輯或使用。
JDK 9的根目錄有如COPYRIGHT和README等文件。 JDK 9中的發(fā)行文件包含一個(gè)帶有MODULES鍵的新條目,其值為映像中包含的模塊列表。 JDK 9映像中的發(fā)行文件的部分內(nèi)容如下所示:
MODULES=java.rmi,jdk.jdi,jdk.policytool
OS_VERSION="5.2"
OS_ARCH="amd64"
OS_NAME="Windows"
JAVA_VERSION="9"
JAVA_FULL_VERSION="9-ea+133"
在列表中只顯示了三個(gè)模塊。 在完整的JDK安裝中,此列表將包括所有平臺模塊。 在自定義運(yùn)行時(shí)映像中,此列表將僅包含你在映像中使用的模塊。
Tips
JDK中的lib\tools.jar和JRE中的lib\rt.jar已從Java SE 9中刪除。這些JAR中可用的類和資源現(xiàn)在以文件中的內(nèi)部格式存儲(chǔ)在lib目錄的命名模塊中。 可以使用稱為jrt的新方案來從運(yùn)行時(shí)映像檢索這些類和資源。 依靠這些JAR位置的應(yīng)用程序?qū)⒉辉俟ぷ鳌?/p>
2. 行為變化
行為變化將影響應(yīng)用程序的運(yùn)行時(shí)行為。 以下部分將說明這些更改。
三. 支持標(biāo)準(zhǔn)覆蓋機(jī)制
在Java SE 9之前,可以使用支持標(biāo)準(zhǔn)的覆蓋機(jī)制來使用更新版本的類和接口來實(shí)現(xiàn)支持標(biāo)準(zhǔn)或獨(dú)立API,如javax.rmi.CORBA包和Java API for XML Processing(JAXP) ,它們是在Java社區(qū)進(jìn)程之外創(chuàng)建的。 這些JAR已經(jīng)被添加到JVM的引導(dǎo)類路徑中,從而覆蓋了JRE中存在的這些類和接口的任何定義。 這些JAR的位置由名為java.endorsed.dirs的系統(tǒng)屬性指定,其中目錄由特定于平臺的路徑分隔符字符分隔。 如果未設(shè)置此屬性,則運(yùn)行時(shí)將在jre\lib\approved目錄中查找JAR。
Java SE 9仍然支持認(rèn)可的標(biāo)準(zhǔn)和獨(dú)立API覆蓋機(jī)制。 在Java SE 9中,運(yùn)行時(shí)映像由模塊組成。 要使用此機(jī)制,需要使用更新版本的模塊,用于支持標(biāo)準(zhǔn)和獨(dú)立API。 需要使用--upgrade-module-path命令行選項(xiàng)。 此選項(xiàng)的值是包含“承認(rèn)標(biāo)準(zhǔn)”和“獨(dú)立API”模塊的目錄列表。 Windows上的以下命令將覆蓋“標(biāo)準(zhǔn)標(biāo)準(zhǔn)”模塊,如JDK 9中的java.corba模塊。將使用umod1和umod2目錄中的模塊而不是運(yùn)行時(shí)映像中的相應(yīng)模塊:
java --upgrade-module-path umod1;umod2
Tips
在Java SE 9中,創(chuàng)建一個(gè)JAVA_HOME\lib\approvaled目錄并設(shè)置名為java.endorsed.dirs的系統(tǒng)屬性,會(huì)產(chǎn)生錯(cuò)誤。
四. 擴(kuò)展機(jī)制
版本9之前的Java SE允許擴(kuò)展機(jī)制,可以通過將JAR放置在系統(tǒng)屬性java.ext.dirs指定的目錄中來擴(kuò)展運(yùn)行時(shí)映像。 如果未設(shè)置此系統(tǒng)屬性,則使用jre\lib\ext目錄作為其默認(rèn)值。 該機(jī)制通過擴(kuò)展類加載器(這是引導(dǎo)類加載器的子類)和系統(tǒng)類加載器的父級加載了該目錄中的所有JAR。 它加載所有應(yīng)用程序類。 這些JAR的內(nèi)容對于在此運(yùn)行時(shí)映像上編譯或運(yùn)行的所有應(yīng)用程序都可見。
Java SE 9不支持?jǐn)U展機(jī)制。 如果需要類似的功能,可以將這些JAR放在類路徑的前面。 使用名為JAVA_HOME\lib\ext的目錄或設(shè)置名為java.ext.dirs的系統(tǒng)屬性會(huì)導(dǎo)致JDK 9中的錯(cuò)誤。
1. 類加載器的改變
在程序運(yùn)行時(shí),每個(gè)類型都由類加載器加載,該類由java.lang.ClassLoader類的一個(gè)實(shí)例表示。 如果你有一個(gè)對象引用obj,你可以通過調(diào)用obj.getClass().getClassLoader()方法獲得它的類加載器引用。 可以使用其getParent()方法獲取類加載器的父類。
在版本9之前,JDK使用三個(gè)類加載器來加載類,如下圖所示。 圖中箭頭方向表示委托方向。 可以添加更多的類加載器,這是ClassLoader類的子類。 來自不同位置和類型的JDK加載類中的三個(gè)類加載器。
版本9之前的JDK中的類加載器層次結(jié)構(gòu)
JDK類加載器以分層方式工作 —— 引導(dǎo)類加載器位于層次結(jié)構(gòu)的頂部。 類加載器將類加載請求委托給上層類加載器。 例如,如果應(yīng)用程序類加載器需要加載一個(gè)類,它將請求委托給擴(kuò)展類加載器,擴(kuò)展類加載器又將請求委托給引導(dǎo)類加載器。 如果引導(dǎo)類加載器無法加載類,擴(kuò)展類加載器將嘗試加載它。 如果擴(kuò)展類加載器無法加載類,則應(yīng)用程序類加載器嘗試加載它。 如果應(yīng)用程序類加載器無法加載它,則拋出ClassNotFoundException異常。
引導(dǎo)類加載器是擴(kuò)展類加載器的父類。 擴(kuò)展類加載器是應(yīng)用程序類加載器的父類。 引導(dǎo)類加載器沒有父類。 默認(rèn)情況下,應(yīng)用程序類加載器將是你創(chuàng)建的其他類加載器的父類。
引導(dǎo)類加載器加載由Java平臺組成的引導(dǎo)類,包括JAVA_HOME\lib\rt.jar中的類和其他幾個(gè)運(yùn)行時(shí)JAR。 它完全在虛擬機(jī)中實(shí)現(xiàn)。 可以使用-Xbootclasspath/p和-Xbootclasspath/a命令行選項(xiàng)來附加引導(dǎo)目錄。 可以使用-Xbootclasspath選項(xiàng)指定引導(dǎo)類路徑,該選項(xiàng)將替換默認(rèn)的引導(dǎo)類路徑。 在運(yùn)行時(shí),sun.boot.class.path系統(tǒng)屬性包含引導(dǎo)類路徑的只讀值。 JDK通過null表示這個(gè)類加載器。 也就是說,你不能得到它的引用。 例如,Object類由引導(dǎo)類加載器加載,并且Object.class.getClassLoade()表達(dá)式將返回null。
擴(kuò)展類加載器用于通過java.ext.dirs系統(tǒng)屬性指定的目錄中的位于JAR中的擴(kuò)展機(jī)制加載可用的類。要獲得擴(kuò)展類加載器的引用,需要獲取應(yīng)用程序類加載器的引用,并在該引用上使用getParent()方法。
應(yīng)用程序類加載器從由CLASSPATH環(huán)境變量指定的應(yīng)用程序類路徑或命令行選項(xiàng)-cp或-classpath加載類。應(yīng)用程序類加載器也稱為系統(tǒng)類加載器,這是一種誤稱,它暗示它加載系統(tǒng)類。可以使用ClassLoader類的靜態(tài)方法getSystemClassLoader()獲取對應(yīng)用程序類加載器的引用。
JDK 9保持三級分層類加載器架構(gòu)以實(shí)現(xiàn)向后兼容。但是,從模塊系統(tǒng)加載類的方式有一些變化。 JDK 9類加載器層次結(jié)構(gòu)如下圖所示。
JDK 9中的加載器層次結(jié)構(gòu)
請注意,在JDK 9中,應(yīng)用程序類加載器可以委托給平臺類加載器以及引導(dǎo)類加載器;平臺類加載器可以委托給引導(dǎo)類加載器和應(yīng)用程序類加載器。 以下詳細(xì)介紹JDK 9類加載器的工作原理。
在JDK 9中,引導(dǎo)類加載器是由類庫和代碼在虛擬機(jī)中實(shí)現(xiàn)的。 為了向后兼容,它在程序中仍然由null表示。 例如,Object.class.getClassLoader()仍然返回null。 但是,并不是所有的Java SE平臺和JDK模塊都由引導(dǎo)類加載器加載。 舉幾個(gè)例子,引導(dǎo)類加載器加載的模塊是java.base,java.logging,java.prefs和java.desktop。 其他Java SE平臺和JDK模塊由平臺類加載器和應(yīng)用程序類加載器加載,這在下面介紹。 JDK 9中不再支持用于指定引導(dǎo)類路徑,-Xbootclasspath和-Xbootclasspath/p選項(xiàng)以及系統(tǒng)屬性sun.boot.class.path。-Xbootclasspath/a選項(xiàng)仍然受支持,其值存儲(chǔ)在jdk.boot.class.path.append的系統(tǒng)屬性中。
JDK 9不再支持?jǐn)U展機(jī)制。 但是,它將擴(kuò)展類加載器保留在名為平臺類加載器的新名稱下。 ClassLoader類包含一個(gè)名為getPlatformClassLoader()的靜態(tài)方法,該方法返回對平臺類加載器的引用。 下表包含平臺類加載器加載的模塊列表。 平臺類加載器用于另一目的。 默認(rèn)情況下,由引導(dǎo)類加載器加載的類將被授予所有權(quán)限。 但是,幾個(gè)類不需要所有權(quán)限。 這些類在JDK 9中已經(jīng)被取消了特權(quán),并且它們被平臺類加載器加載以提高安全性。
下面是JDK 9中由平臺加載器加載的模塊列表。
java.activation
java.xml.ws.annotation
jdk.desktop
java.compiler
javafx.base
jdk.dynalink
java.corba
javafx.controls
jdk.javaws
java.jnlp
javafx.deploy
jdk.jsobject
java.scripting
javafx.fxml
jdk.localedata
java.se
javafx.graphics
jdk.naming.dns
java.se.ee
javafx.media
jdk.plugin
java.security.jgss
javafx.swing
jdk.plugin.dom
java.smartcardio
javafx.web
jdk.plugin.server
java.sql
jdk.accessibility
jdk.scripting.nashorn
java.sql.rowset
jdk.charsets
jdk.security.auth
java.transaction
jdk.crypto.cryptoki
jdk.security.jgss
java.xml.bind
jdk.crypto.ec
jdk.xml.dom
java.xml.crypto
jdk.crypto.mscapi
jdk.zipfs
java.xml.ws
jdk.deploy
應(yīng)用程序類加載器加載在模塊路徑上找到的應(yīng)用程序模塊和一些提供工具或?qū)С龉ぞ逜PI的JDK模塊,如下表所示。 仍然可以使用ClassLoader類的getSystemClassLoader()的靜態(tài)方法來獲取應(yīng)用程序類加載器的引用。
jdk.attach
jdk.jartool
jdk.jstatd
jdk.compiler
jdk.javadoc
jdk.pack
jdk.deploy.controlpanel
jdk.jcmd
jdk.packager
jdk.editpad
jdk.jconsole
jdk.packager.services
jdk.hotspot.agent
jdk.jdeps
jdk.policytool
jdk.internal.ed
jdk.jdi
jdk.rmic
jdk.internal.jvmstat
jdk.jdwp.agent
jdk.scripting.nashorn.shell
jdk.internal.le
jdk.jlink
jdk.xml.bind
jdk.internal.opt
jdk.jshell
jdk.xml.ws
Tips
在JDK 9之前,擴(kuò)展類加載器和應(yīng)用程序類加載器是java.net.URLClassLoader類的一個(gè)實(shí)例。 在JDK 9中,平臺類加載器(以前的擴(kuò)展類加載器)和應(yīng)用程序類加載器是內(nèi)部JDK類的實(shí)例。 如果你的代碼依賴于·URLClassLoader·類的特定方法,代碼可能會(huì)在JDK 9中崩潰。
JDK 9中的類加載機(jī)制有所改變。 三個(gè)內(nèi)置的類加載器一起協(xié)作來加載類。 當(dāng)應(yīng)用程序類加載器需要加載類時(shí),它將搜索定義到所有類加載器的模塊。 如果有合適的模塊定義在這些類加載器中,則該類加載器將加載類,這意味著應(yīng)用程序類加載器現(xiàn)在可以委托給引導(dǎo)類加載器和平臺類加載器。 如果在為這些類加載器定義的命名模塊中找不到類,則應(yīng)用程序類加載器將委托給其父類,即平臺類加載器。 如果類尚未加載,則應(yīng)用程序類加載器將搜索類路徑。 如果它在類路徑中找到類,它將作為其未命名模塊的成員加載該類。 如果在類路徑中找不到類,則拋出ClassNotFoundException異常。
當(dāng)平臺類加載器需要加載類時(shí),它將搜索定義到所有類加載器的模塊。 如果一個(gè)合適的模塊被定義為這些類加載器中,則該類加載器加載該類。 這意味著平臺類加載器可以委托給引導(dǎo)類加載器以及應(yīng)用程序類加載器。 如果在為這些類加載器定義的命名模塊中找不到一個(gè)類,那么平臺類加載器將委托給它的父類,即引導(dǎo)類加載器。
當(dāng)引導(dǎo)類加載器需要加載一個(gè)類時(shí),它會(huì)搜索自己的命名模塊列表。 如果找不到類,它將通過命令行選項(xiàng)-Xbootclasspath/a指定的文件和目錄列表進(jìn)行搜索。 如果它在引導(dǎo)類路徑上找到一個(gè)類,它將作為其未命名模塊的成員加載該類。
你可以看到類加載器及其加載的模塊和類。 JDK 9包含一個(gè)名為-Xlog::modules的選項(xiàng),用于在虛擬機(jī)加載時(shí)記錄調(diào)試或跟蹤消息。 其格式如下:
-Xlog:modules=
此選項(xiàng)產(chǎn)生大量的輸出。 建議將輸出重定向到一個(gè)文件,以便可以輕松查看。 以下命令在Windows上運(yùn)行素?cái)?shù)檢查的客戶端程序,并在test.txt文件中記錄模塊加載信息。 下面顯示部分輸出。 輸出顯示定義模塊的類加載器。
命令:
C:\Java9Revealed>java -Xlog:modules=trace --module-path lib
--module com.jdojo.prime.client/com.jdojo.prime.client.Main > test.txt
部分信息輸出:
[0.022s][trace][modules] Setting package: class: java.lang.Object, package: java/lang, loader:
[0.022s][trace][modules] Setting package: class: java.io.Serializable, package: java/io, loader:
...
[0.855s][debug][modules] define_module(): creation of module: com.jdojo.prime.client, version: NULL, location: file:///C:/Java9Revealed/lib/com.jdojo.prime.client.jar, class loader 0x00000049ec86dd90 a 'jdk/internal/loader/ClassLoaders$AppClassLoader'{0x00000000895d1c98}, package #: 1
[0.855s][trace][modules] define_module(): creation of package com/jdojo/prime/client for module com.jdojo.prime.client
...
五. 訪問資源
資源是應(yīng)用程序使用的數(shù)據(jù),例如圖像,音頻,視頻,文本文件等。Java提供了一種通過在類路徑上定位資源來訪問資源的位置無關(guān)的方式。 需要以與在JAR中打包類文件相同的方式打包資源,并將JAR添加到類路徑。 通常,類文件和資源打包在同一個(gè)JAR中。 訪問資源是每個(gè)Java開發(fā)人員執(zhí)行的重要任務(wù)。 在接下來的章節(jié)中,將在版本9和JDK 9之前解釋JDK中提供可用的API。
1. 在JDK 9之前訪問資源
在本節(jié)中,將解釋如何在版本9之前在JDK中訪問資源。如果你已經(jīng)知道如何在版本9之前訪問JDK中的資源,可以跳到下一節(jié),介紹如何訪問JDK 9中的資源。
在Java代碼中,資源由資源名稱標(biāo)識,資源名稱是由斜線(/)分隔的一串字符串。 對于存儲(chǔ)在JAR中的資源,資源名稱僅僅是存儲(chǔ)在JAR中的文件的路徑。 例如,在JDK 9之前,存儲(chǔ)在rt.jar中的java.lang包中的Object.class文件是一個(gè)資源,其資源名稱是java/lang/Object.class。
在JDK 9之前,可以使用以下兩個(gè)類中的方法來訪問資源:
java.lang.Class
java.lang.ClassLoader
資源由ClassLoader定位。 一個(gè)Class代理中的資源尋找方法到它的ClassLoader。 因此,一旦了解ClassLoader使用的資源加載過程,將不會(huì)在使用Class類的方法時(shí)遇到問題。 在兩個(gè)類中有兩種不同的命名實(shí)例方法:
URL getResource(String name)
InputStream getResourceAsStream(String name)
兩種方法都會(huì)以相同的方式找到資源。 它們的差異僅在于返回類型。 第一個(gè)方法返回一個(gè)URL,而第二個(gè)方法返回一個(gè)InputStream。 第二種方法相當(dāng)于調(diào)用第一種方法,然后在返回的URL對象上調(diào)用openStream()。
Tips
如果找不到指定的資源,所有資源查找方法都將返回null。
ClassLoader類包含三個(gè)額外的查找資源的靜態(tài)方法:
static URL getSystemResource(String name)
static InputStream getSystemResourceAsStream(String name)
static Enumeration
這些方法使用系統(tǒng)類加載器(也稱為應(yīng)用程序類加載器)來查找資源。 第一種方法返回找到的第一個(gè)資源的URL。 第二種方法返回找到的第一個(gè)資源的InputStream。 第三種方法返回使用指定的資源名稱找到的所有資源的URL枚舉。
要找到資源,有兩種類型的方法可以從——getSystemResource *和getResource *中進(jìn)行選擇。 在討論哪種方法是最好的之前,重要的是要了解有兩種類型的資源:
系統(tǒng)資源
非系統(tǒng)資源
你必須了解他們之間的區(qū)別,以了解資源查找機(jī)制。系統(tǒng)資源是在bootstrap類路徑,擴(kuò)展目錄中的JAR和應(yīng)用程序類路徑中找到的資源。非系統(tǒng)資源可以存儲(chǔ)在除路徑之外的位置,例如在特定目錄,網(wǎng)絡(luò)上或數(shù)據(jù)庫中。 getSystemResource()方法使用應(yīng)用程序類加載程序找到一個(gè)資源,委托給它的父類,它是擴(kuò)展類加載器,后者又委托給它的父類(引導(dǎo)類加載器)。如果你的應(yīng)用程序是獨(dú)立的應(yīng)用程序,并且它只使用三個(gè)內(nèi)置的JDK類加載器,那么你將很好的使用名為getSystemResource *的靜態(tài)方法。它將在類路徑中找到所有資源,包括運(yùn)行時(shí)映像中的資源,如rt.jar文件。如果你的應(yīng)用程序是在瀏覽器中運(yùn)行的小程序,或在應(yīng)用程序服務(wù)器和Web服務(wù)器中運(yùn)行的企業(yè)應(yīng)用程序,則應(yīng)使用名為getResource*的實(shí)例方法,它可以使用特定的類加載器來查找資源。如果在Class對象上調(diào)用getResource*方法,則會(huì)使用當(dāng)前類加載器(加載Class對象的類加載器)來查找資源。
傳遞給ClassLoader類中所有方法的資源名稱都是絕對的,它們不以斜線(/)開頭。 例如,當(dāng)調(diào)用ClassLoader的getSystemResource()方法時(shí),將使用java/lang/Object.class作為資源名稱。
Class類中的資源查找方法可以指定絕對和相對資源名稱。 絕對資源名稱以斜線開頭,而相對資源名稱不用。 當(dāng)使用絕對名稱時(shí),Class類中的方法會(huì)刪除前導(dǎo)斜線并委派給加載Class對象的類加載器來查找資源。 以下調(diào)用
Test.class.getResource("/resources/test.config");
會(huì)被轉(zhuǎn)換成
Test.class.getClassLoader().getResource("resources/test.config");
當(dāng)使用相對名稱時(shí),Class類中的方法預(yù)先添加了包名稱,在使用斜線后跟斜線替換包名中的點(diǎn),然后再委托加載Class對象的類加載器來查找資源。 假設(shè)測試類在com.jdojo.test包中,以下調(diào)用:
Test.class.getResource("resources/test.config");
會(huì)被轉(zhuǎn)換成
Test.class.getClassLoader() .getResource("com/jdojo/test/resources/test.config");
我們來看一個(gè)在JDK 9之前查找資源的例子。 使用JDK 8運(yùn)行示例。NetBeans項(xiàng)目名為com.jdojo.resource.preJDK9。 如果你創(chuàng)建自己的項(xiàng)目,請確保將項(xiàng)目的Java平臺和源更改為JDK 8。類和資源的排列如下:
word_to_number.properties
com/jdojo/resource/prejdk9/ResourceTest.class
com/jdojo/resource/prejdk9/resources/number_to_word.properties
該項(xiàng)目包含兩個(gè)資源文件:根目錄下的word_to_number.properties和com/jdojo/resource/prejdk9/resources目錄中的number_to_word.properties。 這兩個(gè)屬性文件的內(nèi)容分別如下所示:
One=1
Two=2
Three=3
Four=4
Five=5
1=One
2=Two
3=Three
4=Four
5=Five
下面包含一個(gè)完整的程序,顯示如何使用不同的類及其方法查找資源。 該程序演示了可以將應(yīng)用程序中的類文件用作資源,可以使用相同的方法找到它們來查找其他類型的資源。
// ResourceTest.java
package com.jdojo.resource.prejdk9;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
public class ResourceTest {
public static void main(String[] args) {
System.out.println("Finding resources using the system class loader:");
findSystemResource("java/lang/Object.class");
findSystemResource("com/jdojo/resource/prejdk9/ResourceTest.class");
findSystemResource("com/jdojo/prime/PrimeChecker.class");
findSystemResource("sun/print/resources/duplex.png");
System.out.println("\nFinding resources using the Class class:");
// A relative resource name - Will not find Object.class
findClassResource("java/lang/Object.class");
// An absolute resource name - Will find Object.class
findClassResource("/java/lang/Object.class");
// A relative resource name - will find the class
findClassResource("ResourceTest.class");
// Load the wordtonumber.properties file
loadProperties("/wordtonumber.properties");
// Will not find the properties because we are using
// an absolute resource name
loadProperties("/resources/numbertoword.properties");
// Will find the properties
loadProperties("resources/numbertoword.properties");
}
public static void findSystemResource(String resource) {
URL url = ClassLoader.getSystemResource(resource);
System.out.println(url);
}
public static URL findClassResource(String resource) {
URL url = ResourceTest.class.getResource(resource);
System.out.println(url);
return url;
}
public static Properties loadProperties(String resource) {
Properties p1 = new Properties();
URL url = ResourceTest.class.getResource(resource);
if (url == null) {
System.out.println("Properties not found: " + resource);
return p1;
}
try {
p1.load(url.openStream());
System.out.println("Loaded properties from " + resource);
System.out.println(p1);
} catch (IOException e) {
System.out.println(e.getMessage());
}
return p1;
}
}
以下是輸出結(jié)果:
Finding resources using the system class loader:
jar:file:/C:/java8/jre/lib/rt.jar!/java/lang/Object.class
file:/C:/Java9Revealed/com.jdojo.resource.prejdk9/build/classes/com/jdojo/resource/prejdk9/ResourceTest.class
null
jar:file:/C:/java8/jre/lib/resources.jar!/sun/print/resources/duplex.png
Finding resources using the Class class:
null
jar:file:/C:/java8/jre/lib/rt.jar!/java/lang/Object.class
file:/C:/Java9Revealed/com.jdojo.resource.prejdk9/build/classes/com/jdojo/resource/prejdk9/ResourceTest.class
Loaded properties from /wordtonumber.properties
{One=1, Three=3, Four=4, Five=5, Two=2}
Properties not found: /resources/numbertoword.properties
Loaded properties from resources/numbertoword.properties
{5=Five, 4=Four, 3=Three, 2=Two, 1=One}
2. 在JDK 9 中訪問資源
在JDK 9之前,可以從類路徑上的任何JAR訪問資源。 在JDK 9中,類和資源封裝在模塊中。 在第一次嘗試中,JDK 9設(shè)計(jì)人員強(qiáng)制執(zhí)行模塊封裝規(guī)則,模塊中的資源必須對該模塊是私有的,因此它們只能在該模塊內(nèi)的代碼中訪問。 雖然這個(gè)規(guī)則在理論上看起來很好,但是對于跨模塊共享資源的框架和加載的類文件作為來自其他模塊的資源,就會(huì)帶來問題。 為了有限地訪問模塊中的資源,做了一些妥協(xié),但是仍然強(qiáng)制執(zhí)行模塊的封裝。 JDK 9包含三類資源查找方法:
java.lang.Class
java.lang.ClassLoader
java.lang.Module
Class和ClassLoader類沒新增任何新的方法。 Module類包含一個(gè)getResourceAsStream(String name)方法,如果找到該資源,返回一個(gè)InputStream;否則返回null。
Java JDK
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。