Java中 Package Sealing 的探秘之旅

轉(zhuǎn)載自:https://blog.csdn.net/technerd/article/details/8945587

簡(jiǎn)介

? ? ? ? 如果沒有JAR (Java Archive)的存在,Java也許不至于像今天這樣大行天下。JAR 是一個(gè)將其他文件或JAR捆綁在一起,并以.jar做為擴(kuò)展名的文件。它使用與Zip相同的格式進(jìn)行壓縮,所以適用于任何支持Zip壓縮的系統(tǒng)環(huán)境。由于是壓縮格式,這使得它在網(wǎng)絡(luò)上的傳輸十分迅速。JAR的manifest文件使得我們可以對(duì)JAR包進(jìn)行數(shù)字簽名,版本控制,包密封(Seal),和設(shè)置程序的執(zhí)行入口等,另外,從Java 5 開始在META-INF文件夾下生成索引文件INDEX.LIST,可以大大縮短了JAR文件中類的加載時(shí)間。

? ? ? ?這篇文章主要探討一下JAR中的Package Sealing功能。這個(gè)功能是在Java 1.2引入的。在生成Jar文件時(shí)我們可以指定是否將整個(gè)Jar或者其中某幾個(gè)Package進(jìn)行密封,如果是將Jar文件整個(gè)進(jìn)行密封,那就意味著其內(nèi)所有的Package都被密封了。Package一旦密封,那么Java 虛擬機(jī)一旦成功裝載密封Package中的某個(gè)類后,其后所有裝載的帶有相同Package名的類必須來自同一個(gè)Jar文件,否則將觸發(fā)Sealing Violation安全異常。閑話不多說,讓我們?cè)谌缦聨讉€(gè)方面開始Package Sealing的探秘之旅吧。

Manifest的Package Sealing 配置

Java源代碼解析 Sealing Violation?

Package Sealing 的益處

Sealing Violation實(shí)例


Manifest的Package Sealing 配置

我們先看看 JAR文件是如何通過manifest文件來對(duì)Jar文件中的Package進(jìn)行密封。主要有兩種密封方式

[plain]?view plain?copy

Manifest-Version:?1.0??


Name:?com/tr/algorithm/test/box/??

Sealed:?true??


Name:?com/tr/algorithm/search/??

Sealed:?true??


Name:?com/tr/multiThreads/??

Sealed:?true??


Name:?com/tr/algorithm/BinaryTree/??

Sealed:?true??

上面的manifest文件中針對(duì)某幾個(gè)package進(jìn)行了密封操作,但下面的manifest文件內(nèi)容是對(duì)Jar文件中所有的package進(jìn)行密封

[plain]?view plain?copy

Manifest-Version:?1.0??

Sealed:?true??

Java源代碼解析 Sealing Violation

知道了如何對(duì)Package進(jìn)行密封后,我們一起來看看Java源代碼中是如何觸發(fā)Sealing Violation。我使用的JDK版本為1.6

[plain]?view plain?copy

java?version?"1.6.0_24"??

Java(TM)?SE?Runtime?Environment?(build?1.6.0_24-b50)??

Java?HotSpot(TM)?Client?VM?(build?19.1-b02,?mixed?mode)??

下面是URLClassLoader中對(duì)Package Sealing進(jìn)行安全校驗(yàn)的代碼. 我添加了一些注釋,方便理解.

[java]?view plain?copy

???String?pkgname?=?name.substring(0,?i);??

???Package?pkg?=?getPackage(pkgname);??

???Manifest?man?=?res.getManifest();??


if?(pkg?!=?null)?{???

/*

?????????????????*?如果當(dāng)前試圖加載的類,例如com.seal.util.DateUtil.java,所對(duì)應(yīng)的Package

??????????*??例如com.seal.util,已經(jīng)被ClassLoader裝載。

?????????????????*/???????

if?(pkg.isSealed())?{??

/*

????*如果當(dāng)前裝載的類,例如com.seal.util.DateUtil.java,所對(duì)應(yīng)的Package?com.seal.util?已經(jīng)被裝載且密封,

????*但當(dāng)前裝載的類的URI和已經(jīng)裝載的Package不同,通俗一點(diǎn)就是來自不同的Jar文件的話

????*將觸發(fā)安全異常。

????*/??

if?(!pkg.isSealed(url))?{??

throw?new?SecurityException(??

"sealing?violation:?package?"?+?pkgname?+?"?is?sealed");??

????}??


}else?{?//如果這個(gè)Package已經(jīng)被裝載,但沒有被密封。??

/*

????*查看當(dāng)前正在裝載的類,例如com.seal.util.DateUtil.java?,所對(duì)應(yīng)的Package?com.seal.util?是否需要密封。

????*如果是的話,將觸發(fā)安全異常。因?yàn)椋琂VM不允許對(duì)已經(jīng)裝載的Package再進(jìn)行密封。

????*/??

if?((man?!=?null)?&&?isSealed(pkgname,?man))?{???

throw?new?SecurityException(??

"sealing?violation:?can't?seal?package?"?+?pkgname?+???

":?already?loaded");??

????}??

}??

}else?{??

/*

*如果當(dāng)前試圖加載的類對(duì)應(yīng)的Package沒有被ClassLoader裝載,則試圖加載該P(yáng)ackage。

*如果Manifest文件非空,則會(huì)使用Manifest來定義這個(gè)Package,當(dāng)然,這包含是否對(duì)Package進(jìn)行密封。

*/??

if?(man?!=?null)?{??

????definePackage(pkgname,?man,?url);??

}else?{??

definePackage(pkgname,null,?null,?null,?null,?null,?null,?null);??

??????????????}??

???}??

總的來說,有兩種情況觸發(fā)關(guān)于Sealing的安全異常

檢查當(dāng)前試圖加載的類對(duì)應(yīng)的Package是否已經(jīng)被JVM裝載且密封。如果已經(jīng)被裝載且密封了,但被密封的Package與當(dāng)前加載的類對(duì)應(yīng)的Package不是來自同一個(gè)Jar文件,將觸發(fā)安全異常

檢查當(dāng)前試圖加載的類對(duì)應(yīng)的 Package是否已經(jīng)被JVM裝載且密封。如果已經(jīng)被裝載但沒有被密封,但當(dāng)前試圖加載的類對(duì)應(yīng)的Package確要試圖進(jìn)行密封操作,將觸發(fā)安全異常。JVM不允許對(duì)已經(jīng)裝載但未密封的Package再進(jìn)行密封操作。

Package Sealing 的益處

Package Sealing所能帶來的好處主要是版本一致性. 我們知道Java 在運(yùn)行時(shí)是嚴(yán)格按照classpath中定義的順序進(jìn)行裝載和檢查,尤其是現(xiàn)在Java開源包滿天飛, 很有可能你的Java應(yīng)用程序或者中間件的classpath中會(huì)在不同的Jar文件中包含同一個(gè)Package的不同版本。這會(huì)使得程序運(yùn)行產(chǎn)生不一致性結(jié)果,很難發(fā)現(xiàn)。

Sealing Violation實(shí)例

我們來看看下面兩個(gè)針對(duì)Sealing Violation的例子,就很容易明白。 ?

版本升級(jí)例子

第一例子的大背景是對(duì)發(fā)布的Jar包進(jìn)行版本升級(jí)給用戶使用。在版本一中我們有DateUtil和StringUtil 兩個(gè)類

[java]?view plain?copy

package?com.seal.util;??


public?class?DateUtil?{??

public?void?DoDateStuff(){??

System.out.println("doing?date?stuffs?in?version?1.0");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?StringUtil?{??

public?void?DoStringStuff(){??

System.out.println("doing?string?stuffs?in?version?1.0");??

????}??

}??

這兩個(gè)類我們發(fā)布為sealed_v1.jar。 該Jar文件已經(jīng)被密封,其Manifest文件內(nèi)容如下

[plain]?view plain?copy

Manifest-Version:?1.0??

Sealed:?true??

在版本二中,我們?cè)黾恿艘粋€(gè)類NumberUtil,同時(shí)更改了DateUtil和StringUtil的邏輯。版本二所發(fā)布的Jar文件為sealed_v2.jar。Manifest文件的內(nèi)容和版本一相同。Java類代碼如下

[java]?view plain?copy

package?com.seal.util;??


public?class?DateUtil?{??

public?void?DoDateStuff(){??

System.out.println("doing?date?stuffs?in?version?2.0");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?StringUtil?{??

public?void?DoStringStuff(){??

System.out.println("doing?string?stuffs?in?version?2.0");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?NumberUtil?{??

public?void?DoNumberStuff(){??

System.out.println("doing?number?stuffs?in?version?2.0");??

????}??

}??

這時(shí)候,對(duì)于客戶而言,可用的Jar版本有版本一sealed_v1.jar和版本二sealed_v2.jar。用戶有個(gè)測(cè)試類如下

[java]?view plain?copy

import?com.seal.util.DateUtil;??

import?com.seal.util.NumberUtil;??

import?com.seal.util.StringUtil;??



public?class?SealTestCase1?{??


public?static?void?main(String[]?args){??

DateUtil?du?=new?DateUtil();??

?????du.DoDateStuff();??

StringUtil?su?=new?StringUtil();??

?????su.DoStringStuff();??


NumberUtil?nu?=new?NumberUtil();??

?????nu.DoNumberStuff();??

??}??

}??

我們執(zhí)行Java命令編譯并執(zhí)行上面的測(cè)試類,假設(shè)上述兩個(gè)Jar文件sealed_v1.jar和sealed_v2.jar,以及SealTestCase1類都存放在目錄D:\sealingTest\case_1中。

執(zhí)行如下命令編譯SealTestCase1類。

javac -classpath .;D:\sealingTest\case_1\sealed_v1.jar;D:\sealingTest\case_1\sealed_v2.jar SealTestCase1.java

執(zhí)行如下命令運(yùn)行SealTestCase1類。

java -classpath .;D:\sealingTest\case_1\sealed_v1.jar;D:\sealingTest\case_1\sealed_v2.jar SealTestCase1

[plain]?view plain?copy

doing?date?stuffs?in?version?1.0??

doing?string?stuffs?in?version?1.0??

Exception?in?thread?"main"?java.lang.SecurityException:?sealing?violation:?package?com.seal.util?is?sealed??

????????at?java.net.URLClassLoader.defineClass(URLClassLoader.java:234)??

????????at?java.net.URLClassLoader.access$000(URLClassLoader.java:58)??

????????at?java.net.URLClassLoader$1.run(URLClassLoader.java:197)??

????????at?java.security.AccessController.doPrivileged(Native?Method)??

????????at?java.net.URLClassLoader.findClass(URLClassLoader.java:190)??

????????at?java.lang.ClassLoader.loadClass(ClassLoader.java:305)??

????????at?sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)??

????????at?java.lang.ClassLoader.loadClass(ClassLoader.java:246)??

????????at?SealTestCase1.main(SealTestCase1.java:14)??

這個(gè)Sealing Violation 正是JDK中定義的第一種情況。請(qǐng)看如下的代碼注釋來進(jìn)一步理解這個(gè)Sealing Violation的產(chǎn)生的根本原因。

[java]?view plain?copy

public?class?SealTestCase1?{??


public?static?void?main(String[]?args){??

/*

??????*?JVM?裝載?com.seal.util.DateUtil類,由于Classpath?中sealed_v1.jar在最前面,

??????*?所以,com.seal.util.DateUtil類來自于sealed_v1.jar。我們知道sealed_v1.jar是被整個(gè)密封的。

??????*?JVM?會(huì)將Package?com.seal.util?進(jìn)行裝載并密封。?

??????*/??

DateUtil?du?=new?DateUtil();??

?????du.DoDateStuff();??


/*

??????????*?同樣,sealed_v1.jar在classpath中比sealed_v2.jar靠前,

??????????*?類?com.seal.util.StringUtil?也會(huì)在sealed_v1.jar中進(jìn)行裝載。

??????????*?由于Package?com.seal.util之前已經(jīng)被裝載并密封,而且類com.seal.util.StringUtil與

??????????*?類com.seal.util.DateUtil?來自于相同的Jar文件。所以,不會(huì)出現(xiàn)什么問題。順利通過安全管理器的校驗(yàn)。

??????????*/??

StringUtil?su?=new?StringUtil();??

?????su.DoStringStuff();??


/*

??????*?JVM試圖裝載類com.seal.util.NumberUtil時(shí),發(fā)現(xiàn)該類根本沒在sealed_v1.jar中

??????*?所以,繼續(xù)查找sealed_v2.jar,并找到該類。

??????*?但JVM發(fā)現(xiàn)之前裝載的Package?com.seal.util?已經(jīng)被裝載并密封,而且來自sealed_v1.jar,

??????*?而這個(gè)類確來自sealed_v2.jar。這是JVM會(huì)報(bào)安全異常錯(cuò)誤。

??????*/??

NumberUtil?nu?=new?NumberUtil();??

?????nu.DoNumberStuff();??

??}??

}??

了解到上面的原因后,我們可以將sealed_v1.jar和sealed_v2.jar在classpath中的位置調(diào)換,然后再執(zhí)行一次

java -classpath .;D:\sealingTest\case_1\sealed_v2.jar;D:\sealingTest\case_1\sealed_v1.jar SealTestCase1

[plain]?view plain?copy

doing?date?stuffs?in?version?2.0??

doing?string?stuffs?in?version?2.0??

doing?number?stuffs?in?version?2.0??

我們看到結(jié)果正常顯示。我們可以看到Package Sealing 保證了版本的一致性。

第三方發(fā)布 vs官方發(fā)布

第二個(gè)例子是某官方發(fā)布了一個(gè)密封的Jar文件?sealed_official_release.jar。其類如下

[java]?view plain?copy

package?com.seal.util;??


public?class?DateUtil?{??

public?void?DoDateStuff(){??

System.out.println("doing?date?stuffs?officially");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?NumberUtil?{??

public?void?DoNumberStuff(){??

System.out.println("doing?number?stuffs?officially");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?StringUtil?{??

public?void?DoStringStuff(){??

System.out.println("doing?string?stuffs?officially");??

????}??

}??

然后,該官方發(fā)布的Jar文件,被某第三方組織更改和發(fā)布。Jar文件的名稱為?unsealed_3rdparty_release.jar。請(qǐng)注意,與原來官方發(fā)布的Jar的另一個(gè)不同是這個(gè)Jar文件是一個(gè)未密封的Jar文件。內(nèi)容如下,

[java]?view plain?copy

package?com.seal.util;??


public?class?DateUtil?{??

public?void?DoDateStuff(){??

System.out.println("doing?modified?date?stuffs?by?third?party");??

????}??

}??

[java]?view plain?copy

package?com.seal.util;??


public?class?StringUtil?{??

public?void?DoStringStuff(){??

System.out.println("doing?modified?string?stuffs?by?third?party");??

????}??

}??

那這時(shí),用戶手里可能有這兩個(gè)不同的Jar文件,并且需要運(yùn)行如下程序

[java]?view plain?copy

import?com.seal.util.DateUtil;??

import?com.seal.util.NumberUtil;??

import?com.seal.util.StringUtil;??





public?class?SealTestCase2?{??

public?static?void?main(String[]?args){??

DateUtil?du?=new?DateUtil();??

?????????????du.DoDateStuff();??

StringUtil?su?=new?StringUtil();??

?????????????su.DoStringStuff();??


NumberUtil?nu?=new?NumberUtil();??

?????????????nu.DoNumberStuff();??

??????????}??

}??

我們假設(shè)上述兩個(gè)Jar文件sealed_official_release.jar,?unsealed_3rdparty_release.jar和類SealTestCase2.java 都在目錄D:\sealingTest\case_2中

運(yùn)行如下命令,編譯類SealTestCase2.java。

javac -classpath .;D:\sealingTest\case_2\unsealed_3rdparty_release.jar;D:\sealingTest\case_2\sealed_official_release.jar SealTestCase2.java

運(yùn)行如下命令運(yùn)行SealTestCase2.java。

java -classpath .;D:\sealingTest\case_2\unsealed_3rdparty_release.jar;D:\sealingTest\case_2\sealed_official_release.jar SealTestCase2

[plain]?view plain?copy

doing?modified?date?stuffs?by?third?party??

doing?modified?string?stuffs?by?third?party??

Exception?in?thread?"main"?java.lang.SecurityException:?sealing?violation:?can't??

?seal?package?com.seal.util:?already?loaded??

????????at?java.net.URLClassLoader.defineClass(URLClassLoader.java:242)??

????????at?java.net.URLClassLoader.access$000(URLClassLoader.java:58)??

????????at?java.net.URLClassLoader$1.run(URLClassLoader.java:197)??

????????at?java.security.AccessController.doPrivileged(Native?Method)??

????????at?java.net.URLClassLoader.findClass(URLClassLoader.java:190)??

????????at?java.lang.ClassLoader.loadClass(ClassLoader.java:305)??

????????at?sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)??

????????at?java.lang.ClassLoader.loadClass(ClassLoader.java:246)??

????????at?SealTestCase2.main(SealTestCase2.java:15)??

這個(gè)Sealing Violation 正是JDK中定義的第二種情況。請(qǐng)看如下的代碼注釋來進(jìn)一步理解這個(gè)Sealing Violation的產(chǎn)生的根本原因。

[java]?view plain?copy

public?class?SealTestCase2?{??

public?static?void?main(String[]?args){??

/*

??????????????*?JVM?裝載?com.seal.util.DateUtil類,由于Classpath?中unsealed_3rdparty_release.jar在最前面,

??????????????*?所以,com.seal.util.DateUtil類來自于unsealed_3rdparty_release.jar。

??????????????*?我們知道unsealed_3rdparty_release.jar是沒有被密封的。

??????????????*?JVM?會(huì)將Package?com.seal.util?進(jìn)行裝載但不會(huì)密封它。?

??????????????*/??

DateUtil?du?=new?DateUtil();??

?????????????du.DoDateStuff();??

/*

??????????????????*?同樣,unsealed_3rdparty_release.jar在classpath中比sealed_official_release.jar靠前,

??????????????????*?類?com.seal.util.StringUtil?也會(huì)在unsealed_3rdparty_release.jar中進(jìn)行裝載。

??????????????????*?由于Package?com.seal.util之前已經(jīng)被裝載但沒有密封,

??????????????????*?所以,不會(huì)出現(xiàn)什么問題。順利通過安全管理器的校驗(yàn)。

??????????????????*/??


StringUtil?su?=new?StringUtil();??

?????????????su.DoStringStuff();??

/*

??????????????*?JVM試圖裝載類com.seal.util.NumberUtil時(shí),發(fā)現(xiàn)該類根本沒在unsealed_3rdparty_release.jar中

??????????????*?所以,繼續(xù)查找sealed_official_release.jar,并找到該類。請(qǐng)注意sealed_official_release.jar

??????????????*?是被密封的。所以,JVM試圖對(duì)Package?com.seal.util?進(jìn)行密封。

??????????????*?但JVM發(fā)現(xiàn)Package?com.seal.util?已經(jīng)被裝載。

??????????????*?這時(shí),JVM會(huì)報(bào)安全異常錯(cuò)誤。

??????????????*/??

NumberUtil?nu?=new?NumberUtil();??

?????????????nu.DoNumberStuff();??????

??????????}??


}??

讓我們將Jar文件sealed_official_release.jar,?unsealed_3rdparty_release.jar的順序在classpath中調(diào)換位置,運(yùn)行如下命令

java -classpath .;D:\sealingTest\case_2\sealed_official_release.jar;D:\sealingTest\case_2\unsealed_3rdparty_release.jar SealTestCase2

[java]?view plain?copy

doing?date?stuffs?officially??

doing?string?stuffs?officially??

doing?number?stuffs?officially??

我們看到客戶類SealTestCase2執(zhí)行的所有類是來自官方發(fā)布的Jar文件。從而,保證了版本的一致性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 當(dāng)然是你的父母啊 今天起來了個(gè)大早,去?醫(yī)院做出國體檢。我到時(shí)已經(jīng)有十幾人在候診室等待了,因?yàn)樘崆邦A(yù)約的緣故我排...
    栗子糕抹茶醬閱讀 447評(píng)論 0 0
  • 版本號(hào):8.3.5 市場(chǎng)分析: 根據(jù)Analysys易觀智庫產(chǎn)業(yè)數(shù)據(jù)庫最新發(fā)布的《中國在線旅游市場(chǎng)季度監(jiān)測(cè)報(bào)告20...
    LOLITA_vip閱讀 9,258評(píng)論 0 5
  • 他和她從小就認(rèn)識(shí)的,因?yàn)樗麄儽舜说母改付颊J(rèn)識(shí)的,所以兩家人經(jīng)常串門。小時(shí)候他和她的父母常會(huì)把他和她湊在一起玩。...
    與萍水相逢閱讀 420評(píng)論 1 2
  • 單強(qiáng)見鐵一鳴控制了尖嘴猴腮,也就不參與打斗只站在旁邊警戒。 鐵一鳴推著尖嘴猴腮向藍(lán)色頭發(fā)靠去,并吼著:“來??!來啊...
    李武_四川閱讀 250評(píng)論 2 13
  • 前兩日,校園在修草坪,一群背著草坪機(jī)、腰圍一條薄毛毯、腳穿一雙及膝靴的人像蠶吃桑葉一樣將手中草坪機(jī)的扇葉伸向那片覆...
    追風(fēng)去啦啦啦閱讀 448評(píng)論 0 1

友情鏈接更多精彩內(nèi)容