<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
    主站蜘蛛池模板: 亚洲美女视频一区| 亚洲国产精品久久久久| 亚洲熟妇久久精品| 成人女人A级毛片免费软件| 久久精品亚洲中文字幕无码麻豆 | 亚洲一区二区三区在线| 99精品在线免费观看| 亚洲a在线视频视频| 一级成人a毛片免费播放| 亚洲午夜精品一区二区| 久久免费的精品国产V∧| 亚洲黄色网址大全| 免费可以看黄的视频s色| 亚洲国产日韩综合久久精品| 麻豆最新国产剧情AV原创免费| 亚洲制服丝袜在线播放| 国产精品视频永久免费播放| 亚洲国产精品久久久久秋霞小| 成年女人看片免费视频播放器| 亚洲日本va一区二区三区| 日韩a在线观看免费观看| 人禽伦免费交视频播放| 亚洲AV中文无码乱人伦下载 | 黄色网址免费在线观看| 亚洲成色在线综合网站| 日本黄网站动漫视频免费| 亚洲性色AV日韩在线观看| xvideos亚洲永久网址| a级成人毛片免费视频高清| 亚洲精品自拍视频| 精品国产免费一区二区| 国产一级a毛一级a看免费人娇| 久久亚洲精品无码VA大香大香| 成年轻人网站色免费看| 一级特黄a大片免费| 亚洲精品国产手机| 又爽又黄无遮挡高清免费视频 | 国产免费播放一区二区| 亚洲国产福利精品一区二区 | 在线亚洲精品福利网址导航| 88av免费观看|