国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Find a way out of the ClassLoader maze(走出ClassLoader迷宮)

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

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

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

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

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

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

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

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

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

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

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

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

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

    * JAXP使用上下文類加載器

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

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

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

    這些類加載器非?;靵y,沒有在J2SE文檔中給以清晰明確的說明。

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

 

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()方法來獲取類加載器對象,并使用其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做決定,給它傳遞了個ClassLoadContext對象作為參數(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()返回的類在ClassLoaderResolver或ResourceLoader使用,這樣做的目的是讓其能找到調(diào)用類的類加載器(上下文加載器總是能通過Thread.currentThread().getContextClassLoader()來獲得)。注意

調(diào)用類是靜態(tài)獲得的,因此這個接口不需現(xiàn)有業(yè)務(wù)方法增加額外的Class參數(shù),而且也適合于靜態(tài)方法和類初始化代碼。具體使用時,可以往這個上下文對象中添加具體部署環(huán)境中所需的其他屬性。
    上面代碼看起來很像Strategy設(shè)計模式,其思想是將“總是使用上下文類加載器”或者“總是使用當(dāng)前類加載器”的決策同具體實現(xiàn)邏輯分離開。往往設(shè)計之初是很難預(yù)測何種類加載策略是合適的,該設(shè)計能夠讓你可以后來修改類加載策略。

這兒有一個缺省實現(xiàn),應(yīng)該可以適應(yī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

    上面代碼的邏輯很簡單:如調(diào)用類的當(dāng)前類加載器和上下文類加載器是父子關(guān)系,則總是選擇子類加載器。對子類加載器可見的資源通常是對父類可見資源的超集,因此如果每個開發(fā)者都遵循J2SE的代理規(guī)則,這樣做大多數(shù)情況下是合適的。
    當(dāng)前類加載器和上下文類加載器是兄弟關(guān)系時,決定使用哪一個是比較困難的。理想情況下,Java運行時不應(yīng)產(chǎn)生這種模糊。但一旦發(fā)生,上面代碼選擇上下文類加載器。這是作者本人的實際經(jīng)驗,絕大多數(shù)情況下應(yīng)該能正常工作。你可以修改這部分代碼來適應(yīng)具體需要。一般來說,上下文類加載器要比當(dāng)前類加載器更適合于框架編程,而當(dāng)前類加載器則更適合于業(yè)務(wù)邏輯編程。
    最后需要檢查一下,以便保證所選類加載器不是系統(tǒng)類加載器的父親,在開發(fā)標(biāo)準(zhǔn)擴(kuò)展類庫時這通常是個好習(xí)慣。
    注意作者故意沒有檢查要加載資源或類的名稱。Java XML API成為J2SE核心的歷程應(yīng)該能讓我們清楚過濾類名并不是好想法。作者也沒有試圖檢查哪個類加載器加載首先成功,而是檢查類加載器的父子關(guān)系,這是更好更有保證的方法。
 
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Java虛擬機-線程上下文類加載器
ClassLoader(2)Boot、App、Ext、線程加載器
類加載器
Ken Wu's Blog ? java類加載器體系結(jié)構(gòu)(含hotswap原理)
深入探討 Java 類加載器
Java類加載器
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服