在上篇文章中,遇到朋友提到上下文加載器的問(wèn)題。一直有很多疑惑。今天在CSDN上正巧看到一篇關(guān)于上下文加載器的譯文(這方面網(wǎng)上相關(guān)的資料真的太少了)。感覺(jué)有所啟發(fā)。于是搜了搜原文。連同譯文一塊作為收藏。
譯文來(lái)自:http://blog.sina.com.cn/u/4b6047bc0100096v
對(duì)于類(lèi)加載器,普通Java應(yīng)用開(kāi)發(fā)人員不需要了解太多。但對(duì)于系統(tǒng)開(kāi)發(fā)人員,正確理解Java的類(lèi)加載器模型是開(kāi)發(fā)Java系統(tǒng)軟件的關(guān)鍵。很久以來(lái),我一直對(duì)ClassLoader許多問(wèn)題感到很模糊,自己也在一直探討ClassLoader的機(jī)制,但苦于Java這方面的文檔太少,許多東西都是自己學(xué)習(xí)JDK源碼和看開(kāi)源系統(tǒng)應(yīng)用項(xiàng)目的代碼總結(jié)出來(lái),很不清晰。前不久在幫朋友做那個(gè)企業(yè)應(yīng)用平臺(tái)時(shí),對(duì)這方面的知識(shí)深入研究和學(xué)習(xí)了一下,遇到的最好的文檔就是這篇文章了。在這兒翻譯出來(lái),與希望寫(xiě)系統(tǒng)代碼的朋友分享。
原文太長(zhǎng),分篇譯出。喜歡看原文的朋友不妨直接閱讀原文。
問(wèn)題:何時(shí)使用Thread.getContextClassLoader()?
這是一個(gè)很常見(jiàn)的問(wèn)題,但答案卻很難回答。這個(gè)問(wèn)題通常在需要?jiǎng)討B(tài)加載類(lèi)和資源的系統(tǒng)編程時(shí)會(huì)遇到。總的說(shuō)來(lái)動(dòng)態(tài)加載資源時(shí),往往需要從三種類(lèi)加載器里選擇:系統(tǒng)或說(shuō)程序的類(lèi)加載器、當(dāng)前類(lèi)加載器、以及當(dāng)前線程的上下文類(lèi)加載器。在程序中應(yīng)該使用何種類(lèi)加載器呢?
系統(tǒng)類(lèi)加載器通常不會(huì)使用。此類(lèi)加載器處理啟動(dòng)應(yīng)用程序時(shí)classpath指定的類(lèi),可以通過(guò)ClassLoader.getSystemClassLoader()來(lái)獲得。所有的ClassLoader.getSystemXXX()接口也是通過(guò)這個(gè)類(lèi)加載器加載的。一般不要顯式調(diào)用這些方法,應(yīng)該讓其他類(lèi)加載器代理到系統(tǒng)類(lèi)加載器上。由于系統(tǒng)類(lèi)加載器是JVM最后創(chuàng)建的類(lèi)加載器,這樣代碼只會(huì)適應(yīng)于簡(jiǎn)單命令行啟動(dòng)的程序。一旦代碼移植到EJB、Web應(yīng)用或者Java Web Start應(yīng)用程序中,程序肯定不能正確執(zhí)行。
因此一般只有兩種選擇,當(dāng)前類(lèi)加載器和線程上下文類(lèi)加載器。當(dāng)前類(lèi)加載器是指當(dāng)前方法所在類(lèi)的加載器。這個(gè)類(lèi)加載器是運(yùn)行時(shí)類(lèi)解析使用的加載器,Class.forName(String)和Class.getResource(String)也使用該類(lèi)加載器。代碼中X.class的寫(xiě)法使用的類(lèi)加載器也是這個(gè)類(lèi)加載器。
線程上下文類(lèi)加載器在Java 2(J2SE)時(shí)引入。每個(gè)線程都有一個(gè)關(guān)聯(lián)的上下文類(lèi)加載器。如果你使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文類(lèi)加載器。如果程序?qū)€程上下文類(lèi)加載器沒(méi)有任何改動(dòng)的話,程序中所有的線程將都使用系統(tǒng)類(lèi)加載器作為上下文類(lèi)加載器。Web應(yīng)用和Java企業(yè)級(jí)應(yīng)用中,應(yīng)用服務(wù)器經(jīng)常要使用復(fù)雜的類(lèi)加載器結(jié)構(gòu)來(lái)實(shí)現(xiàn)JNDI(Java命名和目錄接口)、線程池、組件熱部署等功能,因此理解這一點(diǎn)尤其重要。
為什么要引入線程的上下文類(lèi)加載器?將它引入J2SE并不是純粹的噱頭,由于Sun沒(méi)有提供充分的文檔解釋說(shuō)明這一點(diǎn),這使許多開(kāi)發(fā)者很糊涂。實(shí)際上,上下文類(lèi)加載器為同樣在J2SE中引入的類(lèi)加載代理機(jī)制提供了后門(mén)。通常JVM中的類(lèi)加載器是按照層次結(jié)構(gòu)組織的,目的是每個(gè)類(lèi)加載器(除了啟動(dòng)整個(gè)JVM的原初類(lèi)加載器)都有一個(gè)父類(lèi)加載器。當(dāng)類(lèi)加載請(qǐng)求到來(lái)時(shí),類(lèi)加載器通常首先將請(qǐng)求代理給父類(lèi)加載器。只有當(dāng)父類(lèi)加載器失敗后,它才試圖按照自己的算法查找并定義當(dāng)前類(lèi)。
有時(shí)這種模式并不能總是奏效。這通常發(fā)生在JVM核心代碼必須動(dòng)態(tài)加載由應(yīng)用程序動(dòng)態(tài)提供的資源時(shí)。拿JNDI為例,它的核心是由JRE核心類(lèi)(rt.jar)實(shí)現(xiàn)的。但這些核心JNDI類(lèi)必須能加載由第三方廠商提供的JNDI實(shí)現(xiàn)。這種情況下調(diào)用父類(lèi)加載器(原初類(lèi)加載器)來(lái)加載只有其子類(lèi)加載器可見(jiàn)的類(lèi),這種代理機(jī)制就會(huì)失效。解決辦法就是讓核心JNDI類(lèi)使用線程上下文類(lèi)加載器,從而有效的打通類(lèi)加載器層次結(jié)構(gòu),逆著代理機(jī)制的方向使用類(lèi)加載器。
順便提一下,XML解析API(JAXP)也是使用此種機(jī)制。當(dāng)JAXP還是J2SE擴(kuò)展時(shí),XML解析器使用當(dāng)前累加載器方法來(lái)加載解析器實(shí)現(xiàn)。但當(dāng)JAXP成為J2SE核心代碼后,類(lèi)加載機(jī)制就換成了使用線程上下文加載器,這和JNDI的原因相似。
好了,現(xiàn)在我們明白了問(wèn)題的關(guān)鍵:這兩種選擇不可能適應(yīng)所有情況。一些人認(rèn)為線程上下文類(lèi)加載器應(yīng)成為新的標(biāo)準(zhǔn)。但這在不同JVM線程共享數(shù)據(jù)來(lái)溝通時(shí),就會(huì)使類(lèi)加載器的結(jié)構(gòu)亂七八糟。除非所有線程都使用同一個(gè)上下文類(lèi)加載器。而且,使用當(dāng)前類(lèi)加載器已成為缺省規(guī)則,它們廣泛應(yīng)用在類(lèi)聲明、Class.forName等情景中。即使你想盡可能只使用上下文類(lèi)加載器,總是有這樣那樣的代碼不是你所能控制的。這些代碼都使用代理到當(dāng)前類(lèi)加載器的模式。混雜使用代理模式是很危險(xiǎn)的。
更為糟糕的是,某些應(yīng)用服務(wù)器將當(dāng)前類(lèi)加載器和上下文類(lèi)加器分別設(shè)置成不同的ClassLoader實(shí)例。雖然它們擁有相同的類(lèi)路徑,但是它們之間并不存在父子代理關(guān)系。想想這為什么可怕:記住加載并定義某個(gè)類(lèi)的類(lèi)加載器是虛擬機(jī)內(nèi)部標(biāo)識(shí)該類(lèi)的組成部分,如果當(dāng)前類(lèi)加載器加載類(lèi)X并接著執(zhí)行它,如JNDI查找類(lèi)型為Y的數(shù)據(jù),上下文類(lèi)加載器能夠加載并定義Y,這個(gè)Y的定義和當(dāng)前類(lèi)加載器加載的相同名稱(chēng)的類(lèi)就不是同一個(gè),使用隱式類(lèi)型轉(zhuǎn)換就會(huì)造成異常。
這種混亂的狀況還將在Java中存在很長(zhǎng)時(shí)間。在J2SE中還包括以下的功能使用不同的類(lèi)加載器:
* JNDI使用線程上下文類(lèi)加載器
* Class.getResource()和Class.forName()使用當(dāng)前類(lèi)加載器
* JAXP使用上下文類(lèi)加載器
* java.util.ResourceBundle使用調(diào)用者的當(dāng)前類(lèi)加載器
* URL協(xié)議處理器使用java.protocol.handler.pkgs系統(tǒng)屬性并只使用系統(tǒng)類(lèi)加載器。
* Java序列化API缺省使用調(diào)用者當(dāng)前的類(lèi)加載器
這些類(lèi)加載器非常混亂,沒(méi)有在J2SE文檔中給以清晰明確的說(shuō)明。
該如何選擇類(lèi)加載器?
如若代碼是限于某些特定框架,這些框架有著特定加載規(guī)則,則不要做任何改動(dòng),讓框架開(kāi)發(fā)者來(lái)保證其工作(比如應(yīng)用服務(wù)器提供商,盡管他們并不能總是做對(duì))。如在Web應(yīng)用和EJB中,要使用Class.gerResource來(lái)加載資源。在其他情況下,需要考慮使用下面的代碼,這是作者本人在工作中發(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.
可通過(guò)調(diào)用ClassLoaderResolver.getClassLoader()方法來(lái)獲取類(lèi)加載器對(duì)象,并使用其ClassLoader的接口來(lái)加載類(lèi)和資源。此外還可使用下面的ResourceLoader接口來(lái)取代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)該使用何種類(lèi)加載器的接口是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()返回的類(lèi)在ClassLoaderResolver或ResourceLoader使用,這樣做的目的是讓其能找到調(diào)用類(lèi)的類(lèi)加載器(上下文加載器總是能通過(guò)Thread.currentThread().getContextClassLoader()來(lái)獲得)。注意
調(diào)用類(lèi)是靜態(tài)獲得的,因此這個(gè)接口不需現(xiàn)有業(yè)務(wù)方法增加額外的Class參數(shù),而且也適合于靜態(tài)方法和類(lèi)初始化代碼。具體使用時(shí),可以往這個(gè)上下文對(duì)象中添加具體部署環(huán)境中所需的其他屬性。
上面代碼看起來(lái)很像Strategy設(shè)計(jì)模式,其思想是將“總是使用上下文類(lèi)加載器”或者“總是使用當(dāng)前類(lèi)加載器”的決策同具體實(shí)現(xiàn)邏輯分離開(kāi)。往往設(shè)計(jì)之初是很難預(yù)測(cè)何種類(lèi)加載策略是合適的,該設(shè)計(jì)能夠讓你可以后來(lái)修改類(lèi)加載策略。
這兒有一個(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)用類(lèi)的當(dāng)前類(lèi)加載器和上下文類(lèi)加載器是父子關(guān)系,則總是選擇子類(lèi)加載器。對(duì)子類(lèi)加載器可見(jiàn)的資源通常是對(duì)父類(lèi)可見(jiàn)資源的超集,因此如果每個(gè)開(kāi)發(fā)者都遵循J2SE的代理規(guī)則,這樣做大多數(shù)情況下是合適的。
當(dāng)前類(lèi)加載器和上下文類(lèi)加載器是兄弟關(guān)系時(shí),決定使用哪一個(gè)是比較困難的。理想情況下,Java運(yùn)行時(shí)不應(yīng)產(chǎn)生這種模糊。但一旦發(fā)生,上面代碼選擇上下文類(lèi)加載器。這是作者本人的實(shí)際經(jīng)驗(yàn),絕大多數(shù)情況下應(yīng)該能正常工作。你可以修改這部分代碼來(lái)適應(yīng)具體需要。一般來(lái)說(shuō),上下文類(lèi)加載器要比當(dāng)前類(lèi)加載器更適合于框架編程,而當(dāng)前類(lèi)加載器則更適合于業(yè)務(wù)邏輯編程。
最后需要檢查一下,以便保證所選類(lèi)加載器不是系統(tǒng)類(lèi)加載器的父親,在開(kāi)發(fā)標(biāo)準(zhǔn)擴(kuò)展類(lèi)庫(kù)時(shí)這通常是個(gè)好習(xí)慣。
注意作者故意沒(méi)有檢查要加載資源或類(lèi)的名稱(chēng)。Java XML API成為J2SE核心的歷程應(yīng)該能讓我們清楚過(guò)濾類(lèi)名并不是好想法。作者也沒(méi)有試圖檢查哪個(gè)類(lèi)加載器加載首先成功,而是檢查類(lèi)加載器的父子關(guān)系,這是更好更有保證的方法。
(全文完)
原文:
When should I use
Thread.getContextClassLoader()?
Although not frequently asked, this question is rather tough to correctly answer. It usually comes up during framework programming, when a good deal of dynamic class and resource loading goes on. In general, when loading a resource dynamically, you can choose from at least three classloaders: the system (also referred to as the application) classloader, the current classloader, and the current thread context classloader. The question above refers to the latter. Which classloader is the right one?
One choice I dismiss easily: the system classloader. This classloader handles -classpath
and is programmatically accessible as ClassLoader.getSystemClassLoader()
. All ClassLoader.getSystemXXX()
API methods are also routed through this classloader. You should rarely write code that explicitly uses any of the previous methods and instead let other classloaders delegate to the system one. Otherwise, your code will only work in simple command-line applications, when the system classloader is the last classloader created in the JVM. As soon as you move your code into an Enterprise JavaBean, a Web application, or a Java Web Start application, things are guaranteed to break.
So, now we are down to two choices: current and context classloaders. By definition, a current classloader loads and defines the class to which your current method belongs. This classloader is implied when dynamic links between classes resolve at runtime, and when you use the one-argument version of Class.forName()
, Class.getResource()
, and similar methods. It is also used by syntactic constructs like X.class
class literals (see "Get a load of That Name" for more details).
Thread context classloaders were introduced in Java 2 Platform, Standard Edition (J2SE). Every Thread
has a context classloader associated with it (unless it was created by native code). It is set via the Thread.setContextClassLoader()
method. If you don't invoke this method following a Thread
's construction, the thread will inherit its context classloader from its parent Thread
. If you don't do anything at all in the entire application, all Thread
s will end up with the system classloader as their context classloader. It is important to understand that nowadays this is rarely the case since Web and Java 2 Platform, Enterprise Edition (J2EE) applicatiion servers utilize sophisticated classloader hierarchies for features like Java Naming and Directory Interface (JNDI), thread pooling, component hot redeployment, and so on.
Why do thread context classloaders exist in the first place? They were introduced in J2SE without much fanfare. A certain lack of proper guidance and documentation from Sun Microsystems likely explains why many developers find them confusing.
In truth, context classloaders provide a back door around the classloading delegation scheme also introduced in J2SE. Normally, all classloaders in a JVM are organized in a hierarchy such that every classloader (except for the primordial classloader that bootstraps the entire JVM) has a single parent. When asked to load a class, every compliant classloader is expected to delegate loading to its parent first and attempt to define the class only if the parent fails.
Sometimes this orderly arrangement does not work, usually when some JVM core code must dynamically load resources provided by application developers. Take JNDI for instance: its guts are implemented by bootstrap classes in rt.jar
(starting with J2SE 1.3), but these core JNDI classes may load JNDI providers implemented by independent vendors and potentially deployed in the application's -classpath
. This scenario calls for a parent classloader (the primordial one in this case) to load a class visible to one of its child classloaders (the system one, for example). Normal J2SE delegation does not work, and the workaround is to make the core JNDI classes use thread context loaders, thus effectively "tunneling" through the classloader hierarchy in the direction opposite to the proper delegation.
By the way, the previous paragraph may have reminded you of something else: Java API for XML Parsing (JAXP). Yes, when JAXP was just a J2SE extension, the XML parser factories used the current classloader approach for bootstrapping parser implementations. When JAXP was made part of the J2SE 1.4 core, the classloading changed to use thread context classloaders, in complete analogy with JNDI (and confusing many programmers along the way). See what I mean by lack of guidance from Sun?
After this introduction, I have come to the crux of the matter: neither of the remaining two choices is the right one under all circumstances. Some believe that thread context classloaders should become the new standard strategy. This, however, creates a very messy classloading picture if various JVM threads communicate via shared data, unless all of them use the same context loader instance. Furthermore, delegating to the current classloader is already a legacy rule in some existing situations like class literals or explicit calls to Class.forName()
(which is why, by the way, I recommend (again, see "Get a Load of That Name" avoiding the one-argument version of this method). Even if you make an explicit effort to use only context loaders whenever you can, there will always be some code not under your control that delegates to the current loader. This uncontrolled mixing of delegation strategies sounds rather dangerous.
To make matters worse, certain application servers set context and current classloaders to different ClassLoader
instances that have the same classpaths and yet are not related as a delegation parent and child. Take a second to think about why this is particularly horrendous. Remember that the classloader that loads and defines a class is part of the internal JVM's ID for that class. If the current classloader loads a class X
that subsequently executes, say, a JNDI lookup for some data of type Y
, the context loader could load and define Y
. This Y
definition will differ from the one by the same name but seen by the current loader. Enter obscure class cast and loader constraint violation exceptions.
This confusion will probably stay with Java for some time. Take any J2SE API with dynamic resource loading of any kind and try to guess which loading strategy it uses. Here is a sampling:
- JNDI uses context classloaders
Class.getResource()
and Class.forName()
use the current classloader
- JAXP uses context classloaders (as of J2SE 1.4)
java.util.ResourceBundle
uses the caller's current classloader
- URL protocol handlers specified via
java.protocol.handler.pkgs
system property are looked up in the bootstrap and system classloaders only
- Java Serialization API uses the caller's current classloader by default
Those class and resource loading strategies must be the most poorly documented and least specified area of J2SE.
What is a Java programmer to do?
If your implementation is confined to a certain framework with articulated resource loading rules, stick to them. Hopefully, the burden of making them work will be on whoever has to implement the framework (such as an application server vendor, although they don't always get it right either). For example, always use Class.getResource()
in a Web application or an Enterprise JavaBean.
In other situations, you might consider using a solution I have found useful in personal work. The following class serves as a global decision point for acquiring the best classloader to use at any given time in the application (all classes shown in this article are available with the download):
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.

You acquire a classloader reference by calling the ClassLoaderResolver.getClassLoader()
static method and use the result to load classes and resources via the normal java.lang.ClassLoader
API. Alternatively, you can use this ResourceLoader
API as a drop-in replacement for java.lang.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

The decision of what constitutes the best classloader to use is factored out into a pluggable component implementing the IClassLoadStrategy
interface:
public interface IClassLoadStrategy


{
ClassLoader getClassLoader (ClassLoadContext ctx);
} // End of interface

To help IClassLoadStrategy
make its decision, it is given a ClassLoadContext
object:
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()
returns the class whose code calls into ClassLoaderResolver
or ResourceLoader
. This is so that the strategy implementation can figure out the caller's classloader (the context loader is always available as Thread.currentThread().getContextClassLoader()
). Note that the caller is determined statically; thus, my API does not require existing business methods to be augmented with extra Class
parameters and is suitable for static methods and initializers as well. You can augment this context object with other attributes that make sense in your deployment situation.
All of this should look like a familiar Strategy design pattern to you. The idea is that decisions like "always context loader" or "always current loader" get separated from the rest of your implementation logic. It is hard to know ahead of time which strategy will be the right one, and with this design, you can always change the decision later.
I have a default strategy implementation that should work correctly in 95 percent of real-life situations:
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

The logic above should be easy to follow. If the caller's current and context classloaders are in a parent-child relationship, I always choose the child. The set of resources visible to a child loader is normally a superset of classes visible to its parent, so this feels like the right decision as long as everybody plays by J2SE delegation rules.
It is when the current and the context classloaders are siblings that the right decision is impossible. Ideally, no Java runtime should ever create this ambiguity. When it happens, my code chooses the context loader: a decision based on personal experience of when things work correctly most of the time. Feel free to change that code branch to suit your taste. It is possible that the context loader is a better choice for framework components, and the current loader is better for business logic.
Finally, a simple check ensures that the selected classloader is not a parent of the system classloader. This is a good thing to do if you are developing code that might be deployed as an extension library.
Note that I intentionally do not look at the name of resources or classes that will be loaded. If nothing else, the experience with Java XML APIs becoming part of the J2SE core should have taught you that filtering by class names is a bad idea. Nor do I trial load classes to see which classloader succeeds first. Examining classloader parent-child relationships is a fundamentally better and more predictable approach.
Although Java resource loading remains an esoteric topic, J2SE relies on various load strategies more and more with every major platform upgrade. Java will be in serious trouble if this area is not given some significantly better design considerations. Whether you agree or not, I would appreciate your feedback and any interesting pointers from your personal design experience.
Author Bio
Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior engineer for Trilogy in Austin, Texas.
才發(fā)現(xiàn)自己對(duì)類(lèi)加載器的理解還很膚淺。學(xué)習(xí),再學(xué)習(xí)!
歡迎來(lái)訪!^.^!
本BLOG僅用于個(gè)人學(xué)習(xí)交流!
目的在于記錄個(gè)人成長(zhǎng).
所有文字均屬于個(gè)人理解.
如有錯(cuò)誤,望多多指教!不勝感激!