Play OpenJDK: 允许你的包名?java."开?/span>
本文是Play OpenJDK的第二篇Q介l了(jin)如何H破JDK不允许自定义的包名以"java."开头这一限制。这一技巧对于基于已有的JDK向java.*中添加新c还是有所帮助的?2015.11.02最后更?
无论是经验丰富的JavaE序员,q是Java的初学者,M(x)有一些h或有意或无意地创Z个包名ؓ(f)"java"的类。但Z安全斚w的考虑QJDK不允许应用程序类的包名以"java"开_(d)即不允许javaQjava.fooq样的包名。但javaxQjavaexq样的包名是允许的?br />
1. 例子
比如Q以OpenJDK 8为基Q臆造这样一个例子。笔者想向OpenJDK贡献一个同步的HashMapQ即cSynchronizedHashMapQ而该cȝ包名׃ؓ(f)java.util。SynchronizedHashMap是HashMap的同步代理,׃q两个类是在同一包内QSynchronizedHashMap不仅可以讉KHashMap的publicҎ(gu)与变量,q可以访问HashMap的protected和defaultҎ(gu)与变量。SynchronizedHashMap看v来可能像下面q样Q?br />package java.util;
public class SynchronizedHashMap<K, V> {
private HashMap<K, V> hashMap = null;
public SynchronizedHashMap(HashMap<K, V> hashMap) {
this.hashMap = hashMap;
}
public SynchronizedHashMap() {
this(new HashMap<>());
}
public synchronized V put(K key, V value) {
return hashMap.put(key, value);
}
public synchronized V get(K key) {
return hashMap.get(key);
}
public synchronized V remove(K key) {
return hashMap.remove(key);
}
public synchronized int size() {
return hashMap.size; // 直接调用HashMap.size变量Q而非HashMap.size()Ҏ(gu)
}
}
2. ClassLoader的限?/span>
使用javacȝ译源文gSynchronizedHashMap.javaq没有问题,但在使用~译后的SynchronizedHashMap.classӞJDK的ClassLoader则会(x)拒绝加蝲java.util.SynchronizedHashMap?br />设想有如下的应用E序Q?br />import java.util.SynchronizedHashMap;
public class SyncMapTest {
public static void main(String[] args) {
SynchronizedHashMap<String, String> syncMap = new SynchronizedHashMap<>();
syncMap.put("Key", "Value");
System.out.println(syncMap.get("Key"));
}
}
使用java命o(h)去运行该应用Ӟ?x)报如下错误Q?br />Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.util
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at SyncMapTest.main(SyncMapTest.java:6)
Ҏ(gu)ClassLoader.preDefineClass()的源代码如下Q?br />private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
很清楚地Q该Ҏ(gu)?x)先(g)查待加蝲的类全名(卛_?cd)是否?java."开_(d)如是Q则抛出SecurityException。那么可以尝试修改该Ҏ(gu)的源代码Q以H破q一限制?br />从JDK中的src.zip中拿出java/lang/ClassLoader.java文gQ修改其中的preDefineClassҎ(gu)以去除相关限制。重新编译ClassLoader.javaQ将生成的ClassLoader.classQClassLoader$1.classQClassLoader$2.classQClassLoader$3.classQClassLoader$NativeLibrary.classQClassLoader$ParallelLoaders.class和SystemClassLoaderAction.classL换JDK/jre/lib/rt.jar中对应的cR?br />再次q行SyncMapTestQ却仍然?x)抛出相同的SecurityExceptionQ如下所C:(x)
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.util
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at SyncMapTest.main(SyncMapTest.java:6)
此时是由Ҏ(gu)ClassLoader.defineClass1()抛出的SecurityException。但q是一个nativeҎ(gu)Q那么仅通过修改Java代码是无法解册个问题的(JDK真是层层N?。原来在Hotspot的C++源文件hotspot/src/share/vm/classfile/systemDictionary.cpp中有如下语句Q?br />const char* pkg = "java/";
if (!HAS_PENDING_EXCEPTION &&
!class_loader.is_null() &&
parsed_name != NULL &&
!strncmp((const char*)parsed_name->bytes(), pkg, strlen(pkg))) {
// It is illegal to define classes in the "java." package from
// JVM_DefineClass or jni_DefineClass unless you're the bootclassloader
ResourceMark rm(THREAD);
char* name = parsed_name->as_C_string();
char* index = strrchr(name, '/');
*index = '\0'; // chop to just the package name
while ((index = strchr(name, '/')) != NULL) {
*index = '.'; // replace '/' with '.' in package name
}
const char* fmt = "Prohibited package name: %s";
size_t len = strlen(fmt) + strlen(name);
char* message = NEW_RESOURCE_ARRAY(char, len);
jio_snprintf(message, len, fmt, name);
Exceptions::_throw_msg(THREAD_AND_LOCATION,
vmSymbols::java_lang_SecurityException(), message);
}
修改该文件以去除掉相关限Ӟq按照本pd?a href="http://www.aygfsteel.com/jiangshachina/archive/2015/10/30/427994.html">W一文?/a>中介l的Ҏ(gu)去重新构Z个OpenJDK。那么,q个新的JDK不?x)再对包名有M限制?jin)?br />
3. 覆盖Java核心(j)APIQ?/strong>
开发者们在用主IDE时会(x)发现Q如果工E有多个jar文g或源文g目录中包含相同的c,q些IDE?x)根据用h定的优先U顺序来加蝲q些cR比如,在Eclipse中,右键点击某个Java工程-->属?->Java Build Path-->Order and ExportQ在q里调整各个cd或源文g目录的位|,卛_指定加蝲cȝ优先U?br />当开发者在使用某个开源类?jar文g)Ӟ惛_其中某个c进行修改,那么可以将该类的源代码复制出来Qƈ在Java工程中创Z个同名类Q然后指定Eclipse优先加息自己创徏的类。即Q在~译时与q行时用自己创徏的类去覆盖类库中的同名类。那么,是否可以如法炮制去覆盖Java核心(j)API中的cdQ?br />考虑去覆盖类java.util.HashMapQ只是简单在它的put()Ҏ(gu)d一条打印语。那么就需要将src.zip中的java/util/HashMap.java复制出来Qƈ在当前Java工程中创Z个同名类java.util.HashMapQƈ修改put()Ҏ(gu)Q如下所C:(x)
package java.util;
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
.
public V put(K key, V value) {
System.out.printf("put - key=%s, value=%s%n", key, value);
return putVal(hash(key), key, value, false, true);
}

} 此时Q在Eclipse环境中,SynchronizedHashMap使用的java.util.HashMap被认为是上述新创建的HashMapcR那么运行应用程序SyncMapTest后的期望输出应该如下所C:(x)
put - key=Key, value=Value
Value
但运行SyncMapTest后的实际输出却ؓ(f)如下Q?br />Value
看v来,新创建的java.util.HashMapq没有被使用上。这是ؓ(f)什么呢Q能?惛_"到的原因q是cd载器。关于Javacd载器的讨Z(jin)本文的范_(d)而且关于该主题的文章已是汗牛充栋Q但本文仍会(x)q其要点?br />Javacd载器׃至上分ؓ(f)三个层次Q引导类加蝲?Bootstrap Class Loader)Q扩展类加蝲?Extension Class Loader)和应用程序类加蝲?Application Class Loader)。其中引导类加蝲器用于加载rt.jarq样的核?j)类库。ƈ且引导类加蝲器ؓ(f)扩展cd载器的父加蝲器,而扩展类加蝲器又为应用程序类加蝲器的父加载器。同时JVM在加载类时实行委托模式。即Q当前类加蝲器在加蝲cLQ会(x)首先委托自己的父加蝲器去q行加蝲。如果父加蝲器已l加载了(jin)某个c,那么子加载器不?x)再ơ加载?br />׃可知Q当应用E序试图加蝲java.util.MapӞ它会(x)首先逐向上委托父加载器d载该c,直到引导cd载器加蝲到rt.jar中的java.util.HashMap。由于该cdl被加蝲?jin),我们自己创徏的java.util.HashMap׃?x)被重复加蝲?br />使用java命o(h)q行SyncMapTestE序时加上VM参数-verbose:classQ会(x)在窗口中打印出Ş式如下的语句Q?br />[Opened /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.lang.Object from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]

[Loaded java.util.HashMap from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.util.HashMap$Node from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]

[Loaded java.util.SynchronizedHashMap from file:/home/ubuntu/projects/test/classes/]
Value
[Loaded java.lang.Shutdown from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /home/ubuntu/jdk1.8.0_custom/jre/lib/rt.jar] 从中可以看出Q类java.util.HashMap实是从rt.jar中加载到的。但理论上,可以通过自定义类加蝲器去打破委托模式Q然而这是另一个话题(sh)(jin)?/div> 
]]>