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

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

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

    隨筆-128  評(píng)論-55  文章-5  trackbacks-0
     

    Find a way out of the ClassLoader maze(走出ClassLoader迷宮

     

    對(duì)于類加載器,普通Java應(yīng)用開發(fā)人員不需要了解太多。但對(duì)于系統(tǒng)開發(fā)人員,正確理解Java的類加載器模型是開發(fā)Java系統(tǒng)軟件的關(guān)鍵。很久以來,我一直對(duì)ClassLoader許多問題感到很模糊,自己也在一直探討ClassLoader的機(jī)制,但苦于Java這方面的文檔太少,許多東西都是自己學(xué)習(xí)JDK源碼和看開源系統(tǒng)應(yīng)用項(xiàng)目的代碼總結(jié)出來,很不清晰。這篇文章在此方面做了一些深入的講解。

     

    問題:何時(shí)使用Thread.getContextClassLoader()?

    這是一個(gè)很常見的問題,但答案卻很難回答。這個(gè)問題通常在需要?jiǎng)討B(tài)加載類和資源的系統(tǒng)編程時(shí)會(huì)遇到。總的說來動(dòng)態(tài)加載資源時(shí),往往需要從三種類加載器里選擇:系統(tǒng)或說程序的類加載器、當(dāng)前類加載器、以及當(dāng)前線程的上下文類加載器。在程序中應(yīng)該使用何種類加載器呢?

    系統(tǒng)類加載器通常不會(huì)使用。此類加載器處理啟動(dòng)應(yīng)用程序時(shí)classpath指定的類,可以通過ClassLoader.getSystemClassLoader()來獲得。所有的ClassLoader.getSystemXXX()接口也是通過這個(gè)類加載器加載的。一般不要顯式調(diào)用這些方法,應(yīng)該讓其他類加載器代理到系統(tǒng)類加載器上。由于系統(tǒng)類加載器是JVM最后創(chuàng)建的類加載器,這樣代碼只會(huì)適應(yīng)于簡(jiǎn)單命令行啟動(dòng)的程序。一旦代碼移植到EJBWeb應(yīng)用或者Java Web Start應(yīng)用程序中,程序肯定不能正確執(zhí)行。

    因此一般只有兩種選擇,當(dāng)前類加載器和線程上下文類加載器。當(dāng)前類加載器是指當(dāng)前方法所在類的加載器。這個(gè)類加載器是運(yùn)行時(shí)類解析使用的加載器,Class.forName(String)Class.getResource(String)也使用該類加載器。代碼中X.class的寫法使用的類加載器也是這個(gè)類加載器。

    線程上下文類加載器在Java 2(J2SE)時(shí)引入。每個(gè)線程都有一個(gè)關(guān)聯(lián)的上下文類加載器。如果你使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文類加載器。如果程序?qū)€程上下文類加載器沒有任何改動(dòng)的話,程序中所有的線程將都使用系統(tǒng)類加載器作為上下文類加載器。Web應(yīng)用和Java企業(yè)級(jí)應(yīng)用中,應(yīng)用服務(wù)器經(jīng)常要使用復(fù)雜的類加載器結(jié)構(gòu)來實(shí)現(xiàn)JNDIJava命名和目錄接口)、線程池、組件熱部署等功能,因此理解這一點(diǎn)尤其重要。

    為什么要引入線程的上下文類加載器?將它引入J2SE并不是純粹的噱頭,由于Sun沒有提供充分的文檔解釋說明這一點(diǎn),這使許多開發(fā)者很糊涂。實(shí)際上,上下文類加載器為同樣在J2SE中引入的類加載代理機(jī)制提供了后門。通常JVM中的類加載器是按照層次結(jié)構(gòu)組織的,目的是每個(gè)類加載器(除了啟動(dòng)整個(gè)JVM的原初類加載器)都有一個(gè)父類加載器。當(dāng)類加載請(qǐng)求到來時(shí),類加載器通常首先將請(qǐng)求代理給父類加載器。只有當(dāng)父類加載器失敗后,它才試圖按照自己的算法查找并定義當(dāng)前類。

    有時(shí)這種模式并不能總是奏效。這通常發(fā)生在JVM核心代碼必須動(dòng)態(tài)加載由應(yīng)用程序動(dòng)態(tài)提供的資源時(shí)。拿JNDI為例,它的核心是由JRE核心類(rt.jar)實(shí)現(xiàn)的。但這些核心JNDI類必須能加載由第三方廠商提供的JNDI實(shí)現(xiàn)。這種情況下調(diào)用父類加載器(原初類加載器)來加載只有其子類加載器可見的類,這種代理機(jī)制就會(huì)失效。解決辦法就是讓核心JNDI類使用線程上下文類加載器,從而有效的打通類加載器層次結(jié)構(gòu),逆著代理機(jī)制的方向使用類加載器。

    順便提一下,XML解析API(JAXP)也是使用此種機(jī)制。當(dāng)JAXP還是J2SE擴(kuò)展時(shí),XML解析器使用當(dāng)前累加載器方法來加載解析器實(shí)現(xiàn)。但當(dāng)JAXP成為J2SE核心代碼后,類加載機(jī)制就換成了使用線程上下文加載器,這和JNDI的原因相似。

    好了,現(xiàn)在我們明白了問題的關(guān)鍵:這兩種選擇不可能適應(yīng)所有情況。一些人認(rèn)為線程上下文類加載器應(yīng)成為新的標(biāo)準(zhǔn)。但這在不同JVM線程共享數(shù)據(jù)來溝通時(shí),就會(huì)使類加載器的結(jié)構(gòu)亂七八糟。除非所有線程都使用同一個(gè)上下文類加載器。而且,使用當(dāng)前類加載器已成為缺省規(guī)則,它們廣泛應(yīng)用在類聲明、Class.forName等情景中。即使你想盡可能只使用上下文類加載器,總是有這樣那樣的代碼不是你所能控制的。這些代碼都使用代理到當(dāng)前類加載器的模式。混雜使用代理模式是很危險(xiǎn)的。

    更為糟糕的是,某些應(yīng)用服務(wù)器將當(dāng)前類加載器和上下文類加器分別設(shè)置成不同的ClassLoader實(shí)例。雖然它們擁有相同的類路徑,但是它們之間并不存在父子代理關(guān)系。想想這為什么可怕:記住加載并定義某個(gè)類的類加載器是虛擬機(jī)內(nèi)部標(biāo)識(shí)該類的組成部分,如果當(dāng)前類加載器加載類X并接著執(zhí)行它,如JNDI查找類型為Y的數(shù)據(jù),上下文類加載器能夠加載并定義Y,這個(gè)Y的定義和當(dāng)前類加載器加載的相同名稱的類就不是同一個(gè),使用隱式類型轉(zhuǎn)換就會(huì)造成異常。

    這種混亂的狀況還將在Java中存在很長時(shí)間。在J2SE中還包括以下的功能使用不同的類加載器:

    l        JNDI使用線程上下文類加載器

    l        Class.getResource()Class.forName()使用當(dāng)前類加載器

    l        JAXP使用上下文類加載器

    l        java.util.ResourceBundle使用調(diào)用者的當(dāng)前類加載器

    l        URL協(xié)議處理器使用java.protocol.handler.pkgs系統(tǒng)屬性并只使用系統(tǒng)類加載器。

    l        Java序列化API缺省使用調(diào)用者當(dāng)前的類加載器

     

    這些類加載器非常混亂,沒有在J2SE文檔中給以清晰明確的說明。

    該如何選擇類加載器?

        如若代碼是限于某些特定框架,這些框架有著特定加載規(guī)則,則不要做任何改動(dòng),讓框架開發(fā)者來保證其工作(比如應(yīng)用服務(wù)器提供商,盡管他們并不能總是做對(duì))。如在Web應(yīng)用和EJB中,要使用Class.gerResource來加載資源。在其他情況下,需要考慮使用下面的代碼,這是作者本人在工作中發(fā)現(xiàn)的經(jīng)驗(yàn):

     

    public abstract class ClassLoaderResolver

    {

        /**

         * This method selects the best classloader instance to be used for

         * class/resource loading by whoever calls this method. The decision

         * typically involves choosing between the caller's current, thread context,

         * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}

         * instance established by the last call to {@link #setStrategy}.

         *

         * @return classloader to be used by the caller ['null' indicates the

         * primordial loader] 

         */

        public static synchronized ClassLoader getClassLoader ()

        {

            final Class caller = getCallerClass (0);

            final ClassLoadContext ctx = new ClassLoadContext (caller);

            return s_strategy.getClassLoader (ctx);

        }

        public static synchronized IClassLoadStrategy getStrategy ()

        {

            return s_strategy;

        }

        public static synchronized IClassLoadStrategy setStrategy (final IClassLoadStrategy strategy)

        {

            final IClassLoadStrategy old = s_strategy;

            s_strategy = strategy;

          

            return old;

        }

          

        /**

         * A helper class to get the call context. It subclasses SecurityManager

         * to make getClassContext() accessible. An instance of CallerResolver

         * only needs to be created, not installed as an actual security

         * manager.

         */

        private static final class CallerResolver extends SecurityManager

        {

            protected Class [] getClassContext ()

            {

                return super.getClassContext ();

            }

          

        } // End of nested class

      

      

        /*

         * Indexes into the current method call context with a given

         * offset.

         */

        private static Class getCallerClass (final int callerOffset)

        {      

            return CALLER_RESOLVER.getClassContext () [CALL_CONTEXT_OFFSET +

                callerOffset];

        }

      

        private static IClassLoadStrategy s_strategy; // initialized in <clinit>

      

        private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned

        private static final CallerResolver CALLER_RESOLVER; // set in <clinit>

      

        static

        {

            try

            {

                // This can fail if the current SecurityManager does not allow

                // RuntimePermission ("createSecurityManager"):

              

                CALLER_RESOLVER = new CallerResolver ();

            }

            catch (SecurityException se)

            {

                throw new RuntimeException ("ClassLoaderResolver: could not create CallerResolver: " + se);

            }

          

            s_strategy = new DefaultClassLoadStrategy ();

        }

    } // End of class.

     

        可通過調(diào)用ClassLoaderResolver.getClassLoader()方法來獲取類加載器對(duì)象,并使用其ClassLoader的接口來加載類和資源。此外還可使用下面的ResourceLoader接口來取代ClassLoader接口:

     

    public abstract class ResourceLoader

    {

        /**

         * @see java.lang.ClassLoader#loadClass(java.lang.String)

         */

        public static Class loadClass (final String name)

            throws ClassNotFoundException

        {

            final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);

            return Class.forName (name, false, loader);

        }

        /**

         * @see java.lang.ClassLoader#getResource(java.lang.String)

         */  

        public static URL getResource (final String name)

        {

            final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);

            if (loader != null)

                return loader.getResource (name);

            else

                return ClassLoader.getSystemResource (name);

        }

        ... more methods ...

    } // End of class

        決定應(yīng)該使用何種類加載器的接口是IClassLoaderStrategy

     

    public interface IClassLoadStrategy

    {

        ClassLoader getClassLoader (ClassLoadContext ctx);

    } // End of interface

        為了幫助IClassLoadStrategy做決定,給它傳遞了個(gè)ClassLoadContext對(duì)象作為參數(shù):

     

    public class ClassLoadContext

    {

        public final Class getCallerClass ()

        {

            return m_caller;

        }

      

        ClassLoadContext (final Class caller)

        {

            m_caller = caller;

        }

      

        private final Class m_caller;

    } // End of class

        ClassLoadContext.getCallerClass()返回的類在ClassLoaderResolverResourceLoader使用,這樣做的目的是讓其能找到調(diào)用類的類加載器(上下文加載器總是能通過Thread.currentThread().getContextClassLoader()來獲得)。注意

     

    調(diào)用類是靜態(tài)獲得的,因此這個(gè)接口不需現(xiàn)有業(yè)務(wù)方法增加額外的Class參數(shù),而且也適合于靜態(tài)方法和類初始化代碼。具體使用時(shí),可以往這個(gè)上下文對(duì)象中添加具體部署環(huán)境中所需的其他屬性。

        上面代碼看起來很像Strategy設(shè)計(jì)模式,其思想是將“總是使用上下文類加載器”或者“總是使用當(dāng)前類加載器”的決策同具體實(shí)現(xiàn)邏輯分離開。往往設(shè)計(jì)之初是很難預(yù)測(cè)何種類加載策略是合適的,該設(shè)計(jì)能夠讓你可以后來修改類加載策略。

     

    這兒有一個(gè)缺省實(shí)現(xiàn),應(yīng)該可以適應(yīng)大部分工作場(chǎng)景:

     

    public class DefaultClassLoadStrategy implements IClassLoadStrategy

    {

        public ClassLoader getClassLoader (final ClassLoadContext ctx)

        {

            final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();

            final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();

          

            ClassLoader result;

          

            // If 'callerLoader' and 'contextLoader' are in a parent-child

            // relationship, always choose the child:

           

            if (isChild (contextLoader, callerLoader))

                result = callerLoader;

            else if (isChild (callerLoader, contextLoader))

                result = contextLoader;

            else

            {

                // This else branch could be merged into the previous one,

                // but I show it here to emphasize the ambiguous case:

                result = contextLoader;

            }

            final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();

          

            // Precaution for when deployed as a bootstrap or extension class:

            if (isChild (result, systemLoader))

                result = systemLoader;

          

            return result;

        }

      

        ... more methods ...

    } // End of class

     

        上面代碼的邏輯很簡(jiǎn)單:如調(diào)用類的當(dāng)前類加載器和上下文類加載器是父子關(guān)系,則總是選擇子類加載器。對(duì)子類加載器可見的資源通常是對(duì)父類可見資源的超集,因此如果每個(gè)開發(fā)者都遵循J2SE的代理規(guī)則,這樣做大多數(shù)情況下是合適的。

        當(dāng)前類加載器和上下文類加載器是兄弟關(guān)系時(shí),決定使用哪一個(gè)是比較困難的。理想情況下,Java運(yùn)行時(shí)不應(yīng)產(chǎn)生這種模糊。但一旦發(fā)生,上面代碼選擇上下文類加載器。這是作者本人的實(shí)際經(jīng)驗(yàn),絕大多數(shù)情況下應(yīng)該能正常工作。你可以修改這部分代碼來適應(yīng)具體需要。一般來說,上下文類加載器要比當(dāng)前類加載器更適合于框架編程,而當(dāng)前類加載器則更適合于業(yè)務(wù)邏輯編程。

        最后需要檢查一下,以便保證所選類加載器不是系統(tǒng)類加載器的父親,在開發(fā)標(biāo)準(zhǔn)擴(kuò)展類庫時(shí)這通常是個(gè)好習(xí)慣。

        注意作者故意沒有檢查要加載資源或類的名稱。Java XML API成為J2SE核心的歷程應(yīng)該能讓我們清楚過濾類名并不是好想法。作者也沒有試圖檢查哪個(gè)類加載器加載首先成功,而是檢查類加載器的父子關(guān)系,這是更好更有保證的方法。

    (全文完)



    Author: orangelizq
    email: orangelizq@163.com

    歡迎大家訪問我的個(gè)人網(wǎng)站 萌萌的IT人
    posted on 2009-03-25 22:52 桔子汁 閱讀(597) 評(píng)論(1)  編輯  收藏 所屬分類: J2SE

    評(píng)論:
    # re: [轉(zhuǎn)]走出ClassLoader迷宮 2009-03-27 21:07 | CoderDream
    就算是轉(zhuǎn)帖,也請(qǐng)編輯一下,去掉空行!!!  回復(fù)  更多評(píng)論
      
    主站蜘蛛池模板: 亚洲AV日韩AV永久无码久久| 日韩免费观看视频| 久久国产精品一区免费下载| 国产日韩久久免费影院| 国产免费播放一区二区| sss日本免费完整版在线观看| 特黄特色的大片观看免费视频| 激情小说亚洲图片| 无套内谢孕妇毛片免费看看| 免费无码又爽又黄又刺激网站| 黄网站色视频免费观看45分钟| 成人嫩草影院免费观看| 黄色网址免费在线观看| 国产在线精品一区免费香蕉| 国产精品偷伦视频观看免费| 免费无码又爽又刺激高潮视频| 最好看的中文字幕2019免费| 波多野结衣在线免费视频 | 亚洲国产精品白丝在线观看| 亚洲精品在线免费观看| 亚洲www在线观看| 亚洲av日韩av永久在线观看| 免费国产污网站在线观看不要卡| 国产成人无码精品久久久免费| 老司机69精品成免费视频| 2021精品国产品免费观看| 一二三四影视在线看片免费| 国产资源免费观看| 在线精品亚洲一区二区三区| 亚洲天天在线日亚洲洲精| 激情综合亚洲色婷婷五月| 男人的天堂av亚洲一区2区| 一级片在线免费看| 午夜老司机永久免费看片| 99久久精品日本一区二区免费| 日韩免费无砖专区2020狼| 久久影视国产亚洲| 亚洲国产福利精品一区二区| 亚洲精品一卡2卡3卡四卡乱码| 亚洲免费无码在线| 在线永久看片免费的视频|