在上一篇文章中说到了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.0 Name: com/fjn/java/util/jar/ Sealed: fasle
java_sealed_v2.jar的清单:
Manifest-Version: 1.0 Sealed: 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: hello false file:/E:/workspace/Test/lib/java_sealed_v1.jar [email protected] false file:/E:/workspace/Test/lib/java_sealed_v2.jar id: 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.0 Name: com/fjn/java/util/jar/ Sealed: true
java_sealed_v2.jar#manifest.mf:
Manifest-Version: 1.0 Sealed: false
执行测试,结果如下:
id: 100, name: hello true file:/E:/workspace/Test/lib/java_sealed_v1.jar Exception 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.0 Name: com/fjn/java/util/jar/ Sealed: false
java_sealed_v2.jar#manifest.mf:
Manifest-Version: 1.0 Sealed: true
测试结果如下:
id: 100, name: hello false file:/E:/workspace/Test/lib/java_sealed_v1.jar Exception 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的设置。