標籤:
在上一篇文章中說到了Manifest.mf檔案中可以通過Sealed屬性來指定某些包是否是密封的。那麼到底什麼是密封的,如何來理解它呢?
對於sealed,官方文檔中的說法如下:
JAR files and packages can be optionally sealed so that an package can enforce consistency within a version. A package sealed within a JAR specifies that all classes defined in that package must originate from the same JAR. Otherwise, a SecurityException is thrown. 如果一個package通過JAR檔案清單指定了sealed,那麼這個包下的所有的類都必須是出自同一個jar檔案。不然的話,就出拋出一個SecurityException。
為瞭解決這個疑惑,來做幾個測試吧,通過測試來瞭解sealed:
第1步:在一個package中隨便寫上兩個類:ClassA、ClassB:
在com.fjn.java.util包下有:
ClassA:
package com.fjn.java.util.jar;/** * * @author [email protected] 2015年7月10日 * */public class ClassA { String id = "100"; String name = "hello"; public void showInfo() { System.out.println(this); } @Override public String toString() { return "id: " + this.id + ", name: " + this.name; }}
ClassB:
package com.fjn.java.util.jar;/** * * @author [email protected] 2015年7月10日 * */public class ClassB { public static void main(String[] args) { ClassA obj=new ClassA(); obj.name="hello ,java sealed"; obj.showInfo(); }}
第2步:打包並設定不sealed
現在打包成兩個包(打包時,都設定不sealed):
1)只將ClassA打進包中,打包為java_sealed_v1.jar
2)將com.fjn.java.util整體打包,名字是:java_sealed_v2.jar
java_sealed_v1.jar的清單:
Manifest-Version: 1.0Name: com/fjn/java/util/jar/Sealed: fasle
java_sealed_v2.jar的清單:
Manifest-Version: 1.0Sealed: false
第3步:寫測試案例
建立一個新的project,匯入這兩個jar。測試類別如下:
package com.java.sealtest;import com.fjn.java.util.jar.ClassA;import com.fjn.java.util.jar.ClassB;public class SealedTest { public static void main(String[] args) { ClassA objA=new ClassA(); System.out.println(objA); System.out.println(Package.getPackage("com.fjn.java.util.jar").isSealed()); System.out.println(objA.getClass().getProtectionDomain().getCodeSource().getLocation()); ClassB objB=new ClassB(); System.out.println(objB); System.out.println(Package.getPackage("com.fjn.java.util.jar").isSealed()); System.out.println(objB.getClass().getProtectionDomain().getCodeSource().getLocation()); ClassB.main(new String[0]); }}
第4步:進行測試
測試1)都不使用sealed
執行上述測試案例,結果如下:
id: 100, name: hellofalsefile:/E:/workspace/Test/lib/java_sealed_v1.jar[email protected]falsefile:/E:/workspace/Test/lib/java_sealed_v2.jarid: 100, name: hello ,java sealed
該測試執行成功,從結果中可以看出,在ClassA 類是從java_sealed_v1.jar中載入的、ClassB是從java_sealed_v2.jar中載入的。
測試2)java_sealed_v1.jar中的sealed啟用。
將java_sealed_v1.jar manifest.mf中的sealed設定為true,此時:
java_sealed_v1.jar#manifest.mf:
Manifest-Version: 1.0Name: com/fjn/java/util/jar/Sealed: true
java_sealed_v2.jar#manifest.mf:
Manifest-Version: 1.0Sealed: false
執行測試,結果如下:
id: 100, name: hellotruefile:/E:/workspace/Test/lib/java_sealed_v1.jarException in thread "main" java.lang.SecurityException: sealing violation: package com.fjn.java.util.jar is sealed at java.net.URLClassLoader.getAndVerifyPackage(Unknown Source) at java.net.URLClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.access$100(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at com.java.sealtest.SealedTest.main(SealedTest.java:14)
從這個結果上看,應該是程式執行到ClassB objB=new ClassB();這句時出錯了。
在執行這個語句時,要載入ClassB,jvm在java_sealed_v2.jar中找到了ClassB,找到後要執行getAndVerifyPackage方法。在這個過程中出錯。
現在來看一下URLClassLoader#getAndVerifyPackage()方法:
private Package getAndVerifyPackage(String pkgname, Manifest man, URL url) {// 從當前ClassLoader已經載入的包集合中尋找,這個包是否已經載入過了// 如果已經載入過了,傳回值pkg就不是null. Package pkg = getPackage(pkgname); if (pkg != null) { // Package found, so check package sealing. if (pkg.isSealed()) { // Verify that code source URL is the same. if (!pkg.isSealed(url)) { throw new SecurityException( "sealing violation: package " + pkgname + " is sealed"); } } else { // Make sure we are not attempting to seal the package // at this code source URL. if ((man != null) && isSealed(pkgname, man)) { throw new SecurityException( "sealing violation: can‘t seal package " + pkgname + ": already loaded"); } } } return pkg; }
從ClassLoader已經載入的包中找到了java_sealed_v1.jar下的com.fjn.java.util.jar 包,這個包是密封的,所以就拋出錯誤了。
從上面這段代碼,還能看出另外一個問題:如果一個未密封的包被載入了,再次載入同包名不同jar檔案中類時,也會出錯。
測試3)java_sealed_v1.jar中的sealed禁用、java_sealed_v2.jar中的sealed啟用。這個測試就是用於驗證上面說的另外一種情況的。
此時清單狀態如下:
java_sealed_v1.jar#manifest.mf:
Manifest-Version: 1.0Name: com/fjn/java/util/jar/Sealed: false
java_sealed_v2.jar#manifest.mf:
Manifest-Version: 1.0Sealed: true
測試結果如下:
id: 100, name: hellofalsefile:/E:/workspace/Test/lib/java_sealed_v1.jarException in thread "main" java.lang.SecurityException: sealing violation: can‘t seal package com.fjn.java.util.jar: already loaded at java.net.URLClassLoader.getAndVerifyPackage(Unknown Source) at java.net.URLClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.access$100(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at com.java.sealtest.SealedTest.main(SealedTest.java:14)
測試結果驗證了上面的說法。
從這幾個測試中知道:
在載入類的時候,如果要載入的類 所在的包,在多個jar檔案中,只要有一個被指定了sealed,運行時就有可能出現問題。
如果一個package(package名相同即為同一個包),存在於多個jar檔案中,最好是都不要限制為sealed。
在一個project中,如果某個jar多個版本共存時,一定要注意sealed的設定。
Java Jar : sealed in manifest