<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁 ::  :: 聯系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評論 :: 0 Trackbacks
    Play OpenJDK: 允許你的包名以"java."開頭

    本文是Play OpenJDK的第二篇,介紹了如何突破JDK不允許自定義的包名以"java."開頭這一限制。這一技巧對于基于已有的JDK向java.*中添加新類還是有所幫助的。(2015.11.02最后更新)

    無論是經驗豐富的Java程序員,還是Java的初學者,總會有一些人或有意或無意地創建一個包名為"java"的類。但出于安全方面的考慮,JDK不允許應用程序類的包名以"java"開頭,即不允許java,java.foo這樣的包名。但javax,javaex這樣的包名是允許的。

    1. 例子
    比如,以OpenJDK 8為基礎,臆造這樣一個例子。筆者想向OpenJDK貢獻一個同步的HashMap,即類SynchronizedHashMap,而該類的包名就為java.util。SynchronizedHashMap是HashMap的同步代理,由于這兩個類是在同一包內,SynchronizedHashMap不僅可以訪問HashMap的public方法與變量,還可以訪問HashMap的protected和default方法與變量。SynchronizedHashMap看起來可能像下面這樣:
    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變量,而非HashMap.size()方法
        }
    }

    2. ClassLoader的限制
    使用javac去編譯源文件SynchronizedHashMap.java并沒有問題,但在使用編譯后的SynchronizedHashMap.class時,JDK的ClassLoader則會拒絕加載java.util.SynchronizedHashMap。
    設想有如下的應用程序:
    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命令去運行該應用時,會報如下錯誤:
    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)
    方法ClassLoader.preDefineClass()的源代碼如下:
    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;
    }
    很清楚地,該方法會先檢查待加載的類全名(即包名+類名)是否以"java."開頭,如是,則拋出SecurityException。那么可以嘗試修改該方法的源代碼,以突破這一限制。
    從JDK中的src.zip中拿出java/lang/ClassLoader.java文件,修改其中的preDefineClass方法以去除相關限制。重新編譯ClassLoader.java,將生成的ClassLoader.class,ClassLoader$1.class,ClassLoader$2.class,ClassLoader$3.class,ClassLoader$NativeLibrary.class,ClassLoader$ParallelLoaders.class和SystemClassLoaderAction.class去替換JDK/jre/lib/rt.jar中對應的類。
    再次運行SyncMapTest,卻仍然會拋出相同的SecurityException,如下所示:
    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)
    此時是由方法ClassLoader.defineClass1()拋出的SecurityException。但這是一個native方法,那么僅通過修改Java代碼是無法解決這個問題的(JDK真是層層設防啊)。原來在Hotspot的C++源文件hotspot/src/share/vm/classfile/systemDictionary.cpp中有如下語句:
    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);
    }
    修改該文件以去除掉相關限制,并按照本系列的第一篇文章中介紹的方法去重新構建一個OpenJDK。那么,這個新的JDK將不會再對包名有任何限制了。

    3. 覆蓋Java核心API?
    開發者們在使用主流IDE時會發現,如果工程有多個jar文件或源文件目錄中包含相同的類,這些IDE會根據用戶指定的優先級順序來加載這些類。比如,在Eclipse中,右鍵點擊某個Java工程-->屬性-->Java Build Path-->Order and Export,在這里調整各個類庫或源文件目錄的位置,即可指定加載類的優先級。
    當開發者在使用某個開源類庫(jar文件)時,想對其中某個類進行修改,那么就可以將該類的源代碼復制出來,并在Java工程中創建一個同名類,然后指定Eclipse優先加息自己創建的類。即,在編譯時與運行時用自己創建的類去覆蓋類庫中的同名類。那么,是否可以如法炮制去覆蓋Java核心API中的類呢?
    考慮去覆蓋類java.util.HashMap,只是簡單在它的put()方法添加一條打印語。那么就需要將src.zip中的java/util/HashMap.java復制出來,并在當前Java工程中創建一個同名類java.util.HashMap,并修改put()方法,如下所示:
    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, falsetrue);
        }
        
    }
    此時,在Eclipse環境中,SynchronizedHashMap使用的java.util.HashMap被認為是上述新創建的HashMap類。那么運行應用程序SyncMapTest后的期望輸出應該如下所示:
    put - key=Key, value=Value
    Value
    但運行SyncMapTest后的實際輸出卻為如下:
    Value
    看起來,新創建的java.util.HashMap并沒有被使用上。這是為什么呢?能夠"想像"到的原因還是類加載器。關于Java類加載器的討論超出了本文的范圍,而且關于該主題的文章已是汗牛充棟,但本文仍會簡述其要點。
    Java類加載器由下至上分為三個層次:引導類加載器(Bootstrap Class Loader),擴展類加載器(Extension Class Loader)和應用程序類加載器(Application Class Loader)。其中引導類加載器用于加載rt.jar這樣的核心類庫。并且引導類加載器為擴展類加載器的父加載器,而擴展類加載器又為應用程序類加載器的父加載器。同時JVM在加載類時實行委托模式。即,當前類加載器在加載類時,會首先委托自己的父加載器去進行加載。如果父加載器已經加載了某個類,那么子加載器將不會再次加載。
    由上可知,當應用程序試圖加載java.util.Map時,它會首先逐級向上委托父加載器去加載該類,直到引導類加載器加載到rt.jar中的java.util.HashMap。由于該類已經被加載了,我們自己創建的java.util.HashMap就不會被重復加載。
    使用java命令運行SyncMapTest程序時加上VM參數-verbose:class,會在窗口中打印出形式如下的語句:
    [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]
    從中可以看出,類java.util.HashMap確實是從rt.jar中加載到的。但理論上,可以通過自定義類加載器去打破委托模式,然而這就是另一個話題了。
    posted on 2015-11-01 20:06 John Jiang 閱讀(3818) 評論(0)  編輯  收藏 所屬分類: JavaSEJava原創OpenJDK
    主站蜘蛛池模板: 亚洲av日韩av无码av| 日韩高清在线免费观看| 亚洲国产一二三精品无码| 疯狂做受xxxx高潮视频免费| 毛色毛片免费观看| 亚洲欧洲另类春色校园网站| 1000部国产成人免费视频| 亚洲男人电影天堂| 美女裸身网站免费看免费网站| 亚洲熟妇av一区| 免费精品人在线二线三线区别| 亚洲人成网站在线观看播放青青| 最好看最新的中文字幕免费| 亚洲黄色在线电影| ww4545四虎永久免费地址| 亚洲AV无码一区二区三区牛牛| 九九九精品成人免费视频| 亚洲精品又粗又大又爽A片| 国产精品无码一区二区三区免费| 精品亚洲成a人在线观看| 亚洲精品无码你懂的网站| 成人A毛片免费观看网站| 久久精品国产亚洲av四虎| 18禁止看的免费污网站| 亚洲性色AV日韩在线观看| 免费又黄又爽的视频| a级毛片黄免费a级毛片| 91亚洲视频在线观看| 全部免费国产潢色一级| 精品国产福利尤物免费| 亚洲美女视频一区二区三区| 破了亲妺妺的处免费视频国产| 色噜噜狠狠色综合免费视频| 亚洲精品无码永久在线观看你懂的 | 亚洲成A∨人片天堂网无码| 免费观看一区二区三区| 亚洲专区一路线二| 亚洲精品无码久久毛片| 国产成人精品免费视| 午夜在线免费视频 | 亚洲精品成a人在线观看|