你的位置:首页 > Java教程

[Java教程]Java Jar : sealed in manifest


在上一篇文章中说到了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 fs1194361820@163.com 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 fs1194361820@163.com 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.jarcom.fjn.java.util.jar.ClassB@a8f0b4falsefile:/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的设置。