cuiyi's blog(崔毅 crazycy)

记录点滴 鉴往事之得失 以资于发展
数据加载中……

ClassLoader专题(三):引文

ClassLoader 专题(一): ClassLoader 基础

ClassLoader 专题(二):从 Servlet 容器看 ClassLoader 机制的妙用
    ClassLoader专题(三):引文

 

Find a way out of the ClassLoader maze

System, current, context? Which ClassLoader should you use?

By Vladimir Roubtsov, JavaWorld.com, 06/06/03

June 6, 2003

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 Threads 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) application 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 interfac              

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.

 

Resources

Download the source code for all classes discussed in this article
http:
//www.javaworld.com/javaworld/javaqa/2003-06/load/01-qa-0606-load.zip 

"Get a Load of That Name!" Vladimir Roubtsov (JavaWorld, March 2003)
http:
//www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html 

Ted Neward
's "Understanding Class.forName()" examines similar topics in great detail
http://www.javageeks.com/Papers/ClassForName/index.html 

Sun
's bug database contains multiple issues related to getContextClassLoader. See these bug IDs, for example (requires login)4648098, 4630895, 4452042, 4340158, 4155645
http://developer.java.sun.com/developer/bugParade/ 

Want more
? See the Java Q&A index page for the full Q&A catalog
http:
//www.javaworld.com/columns/jw-qna-index.shtml 

For more than 
100 insightful Java tips, visit JavaWorld's Java Tips index page
http://www.javaworld.com/columns/jw-tips-index.shtml 

Browse the Java 
2 Platform, Standard Edition section of JavaWorld's Topical Index
http://www.javaworld.com/channel_content/jw-j2se-index.shtml?j2se 

Browse the Java Virtual Machine section of JavaWorld
's Topical Index
http://www.javaworld.com/channel_content/jw-jvm-index.shtml 

Sign up 
for JavaWorld's free weekly email newsletters
http://www.javaworld.com/subscribe


·

  

问题:何时使用Thread.getContextClassLoader()?

    这是一个很常见的问题,但答案却很难回答。这个问题通常在需要动态加载类和资源的系统编程时会遇到。总的说来动态加载资源时,往往需要从三种类加载器里选择:系统或说程序的类加载器、当前类加载器、以及当前线程的上下文类加载器。在程序中应该使用何种类加载器呢?

    系统类加载器通常不会使用。此类加载器处理启动应用程序时classpath指定的类,可以通过ClassLoader.getSystemClassLoader()来获得。所有的ClassLoader.getSystemXXX()接口也是通过这个类加载器加载的。一般不要显式调用这些方法,应该让其他类加载器代理到系统类加载器上。由于系统类加载器是JVM最后创建的类加载器,这样代码只会适应于简单命令行启动的程序。一旦代码移植到EJB、Web应用或者Java Web Start应用程序中,程序肯定不能正确执行。

    因此一般只有两种选择,当前类加载器和线程上下文类加载器。当前类加载器是指当前方法所在类的加载器。这个类加载器是运行时类解析使用的加载器,Class.forName(String)和Class.getResource(String)也使用该类加载器。代码中X.class的写法使用的类加载器也是这个类加载器。

    线程上下文类加载器在Java 2(J2SE)时引入。每个线程都有一个关联的上下文类加载器。如果你使用new Thread()方式生成新的线程,新线程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系统类加载器作为上下文类加载器。Web应用和Java企业级应用中,应用服务器经常要使用复杂的类加载器结构来实现JNDI(Java命名和目录接口)、线程池、组件热部署等功能,因此理解这一点尤其重要。

    为什么要引入线程的上下文类加载器?将它引入J2SE并不是纯粹的噱头,由于Sun没有提供充分的文档解释说明这一点,这使许多开发者很糊涂。实际上,上下文类加载器为同样在J2SE中引入的类加载代理机制提供了后门。通常JVM中的类加载器是按照层次结构组织的,目的是每个类加载器(除了启动整个JVM的原初类加载器)都有一个父类加载器。当类加载请求到来时,类加载器通常首先将请求代理给父类加载器。只有当父类加载器失败后,它才试图按照自己的算法查找并定义当前类。

    有时这种模式并不能总是奏效。这通常发生在JVM核心代码必须动态加载由应用程序动态提供的资源时。拿JNDI为例,它的核心是由JRE核心类(rt.jar)实现的。但这些核心JNDI类必须能加载由第三方厂商提供的JNDI实现。这种情况下调用父类加载器(原初类加载器)来加载只有其子类加载器可见的类,这种代理机制就会失效。解决办法就是让核心JNDI类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆着代理机制的方向使用类加载器。(bad translation, cuiyi add)

    顺便提一下,XML解析API(JAXP)也是使用此种机制。当JAXP还是J2SE扩展时,XML解析器使用当前累加载器方法来加载解析器实现。但当JAXP成为J2SE核心代码后,类加载机制就换成了使用线程上下文加载器,这和JNDI的原因相似。

    好了,现在我们明白了问题的关键:这两种选择不可能适应所有情况。一些人认为线程上下文类加载器应成为新的标准。但这在不同JVM线程共享数据来沟通时,就会使类加载器的结构乱七八糟。除非所有线程都使用同一个上下文类加载器。而且,使用当前类加载器已成为缺省规则,它们广泛应用在类声明、Class.forName等情景中。即使你想尽可能只使用上下文类加载器,总是有这样那样的代码不是你所能控制的。这些代码都使用代理到当前类加载器的模式。混杂使用代理模式是很危险的。

    更为糟糕的是,某些应用服务器将当前类加载器和上下文类加器分别设置成不同的ClassLoader实例。虽然它们拥有相同的类路径,但是它们之间并不存在父子代理关系。想想这为什么可怕:记住加载并定义某个类的类加载器是虚拟机内部标识该类的组成部分,如果当前类加载器加载类X并接着执行它,如JNDI查找类型为Y的数据,上下文类加载器能够加载并定义Y,这个Y的定义和当前类加载器加载的相同名称的类就不是同一个,使用隐式类型转换就会造成异常。

    这种混乱的状况还将在Java中存在很长时间。在J2SE中还包括以下的功能使用不同的类加载器:

    * JNDI使用线程上下文类加载器

    * Class.getResource()和Class.forName()使用当前类加载器

    * JAXP使用上下文类加载器

    * java.util.ResourceBundle使用调用者的当前类加载器

    * URL协议处理器使用java.protocol.handler.pkgs系统属性并只使用系统类加载器。

    * Java序列化API缺省使用调用者当前的类加载器

    这些类加载器非常混乱,没有在J2SE文档中给以清晰明确的说明。

posted on 2007-05-05 02:03 crazycy 阅读(2313) 评论(0)  编辑  收藏 所属分类: JavaSE语言


只有注册用户登录后才能发表评论。


网站导航: