轉(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í)例
我們先看看 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??
知道了如何對(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所能帶來的好處主要是版本一致性. 我們知道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)。
我們來看看下面兩個(gè)針對(duì)Sealing Violation的例子,就很容易明白。 ?
第一例子的大背景是對(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 保證了版本的一致性。
第二個(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文件。從而,保證了版本的一致性。