`
klcwt
  • 浏览: 194593 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

线程上下文类加载器

    博客分类:
  • java
阅读更多

线程上下文类加载器

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

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

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

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

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

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

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

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

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

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

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

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

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

l        JAXP使用上下文类加载器

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

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

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

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

该如何选择类加载器?

如若代码是限于某些特定框架,这些框架有着特定加载规则,则不要做任何改动,让框架开发者来保证其工作(比如应用服务器提供商,尽管他们并不能总是做对)。如在Web应用和EJB中,要使用Class.gerResource来加载资源。在其他情况下,需要考虑使用下面的代码,这是作者本人在工作中发现的经验:

 

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.

 

    可通过调用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

    决定应该使用何种类加载器的接口是IClassLoaderStrategy

margin-top: 0pt; margin-bottom: 0

分享到:
评论
3 楼 546144153 2013-07-05  
sezelee 写道
这通常发生在JVM核心代码必须动态加载由应用程序动态提供的资源时。拿JNDI为例,它的核心是由JRE核心类(rt.jar)实现的。但这些核心JNDI类必须能加载由第三方厂商提供的JNDI实现。这种情况下调用父类加载器(原初类加载器)来加载只有其子类加载器可见的类,这种代理机制就会失效。

为什么 一定要调用父类加载器, 难道 “接口” 由启动加载器加载。

“实现类”由系统加载器加载 就不可以吗?????

    你没有说到点子上, 只是重复别人的东西。


这个没有看懂,哪位大神能不能解释下。
它的核心和他的实现类最终是哪个类加载器实现的?是不是同一个类加载器?导致这样结果的原因,能不能帮忙概括下。
2 楼 gexp.fang 2011-11-29  
因为 JDK核心类的SPI'接口'(通常是抽象类)中通常是使用类工厂模式来获取第三方SPI实现的实例,而SPI接口本身是由启动类加载器(或者扩展)加载的,而第三方SPI实现是由系统类加载器加载,层次委托模型的父加载器无法找到系统类加载器加载的类。
而使用线程上下文加载器就可以贯穿线程上下文,使用同一个类加载器加载,自然就解决了委托模型的问题。
参看 JAXP SPI就可以发现,其中DocumentBuilderFactory.newInstance就是使用了线程上下文类加载器来加载第三方实现。
1 楼 sezelee 2011-08-04  
这通常发生在JVM核心代码必须动态加载由应用程序动态提供的资源时。拿JNDI为例,它的核心是由JRE核心类(rt.jar)实现的。但这些核心JNDI类必须能加载由第三方厂商提供的JNDI实现。这种情况下调用父类加载器(原初类加载器)来加载只有其子类加载器可见的类,这种代理机制就会失效。

为什么 一定要调用父类加载器, 难道 “接口” 由启动加载器加载。

“实现类”由系统加载器加载 就不可以吗?????

    你没有说到点子上, 只是重复别人的东西。

相关推荐

    走出ClassLoader误区

    在Java中,有多种类加载器,包括系统类加载器、当前类加载器和线程上下文类加载器。系统类加载器,通常由`ClassLoader.getSystemClassLoader()`获取,主要负责加载启动应用时由classpath指定的类。由于它与JVM启动...

    Java类加载器学习总结.pdf

    为了实现SPI,通常会使用`java.util.ServiceLoader`类,这个类利用了线程上下文类加载器来查找和加载实现类。 类加载器的双亲委托模型虽然在大多数情况下能很好地工作,但在某些特定场景下,如插件系统、模块化系统...

    深入探讨 Java 类加载器

    线程上下文类加载器(Thread Context ClassLoader)是一个特殊的角色,它允许在多线程环境中控制类的加载。每个线程都有一个与之关联的类加载器,可以通过Thread.currentThread().getContextClassLoader()获取。这在...

    java类加载器和核心机制.pdf

    线程上下文类加载器则允许在特定线程上下文中改变类加载器,以便加载特定的类。 服务器类加载原理则涉及到服务器如何管理类的加载以及OSGI(Open Service Gateway Initiative)的技术介绍。OSGI提供了一个运行时...

    Java类加载原理解析

    本文主要解析Java类加载的原理,分为三个部分:基础的类加载原理解析、插件环境下的类加载和线程上下文类加载器。 首先,Java虚拟机(JVM)内置了三种预定义的类加载器: 1. 启动(Bootstrap)类加载器:这是最基础的...

    浅析ClassLoader

    2. 应用场景:Spring框架中,线程上下文类加载器用于加载Bean定义等配置信息,实现灵活的依赖注入。 四、案例分析 文件`ContextClassLoaderTest`和`ContextClassLoaderTest2`可能是用来测试上下文类加载器的示例...

    Java类加载原理解析文档

    本文将详细解析Java类加载原理,分为三篇文章进行阐述,分别是:Java类加载原理解析、插件环境下类加载原理解析和线程上下文类加载器。 首先,我们来了解Java虚拟机(JVM)的类加载器结构。JVM预定义了三种主要的类...

    White paper

    在扩展区中,可以将线程上下文类加载器设置为应用程序类加载器,这样当`Class.forName`在该线程中被调用时,它将使用正确的类加载器加载类。 #### 结论 动态加载类是Java灵活性的重要组成部分,`Class.forName`是...

    Java类加载机制学习1

    线程上下文类加载器(Thread Context ClassLoader)是Java提供的一种机制,允许线程在运行时指定一个类加载器,确保类由同一个类加载器加载。这对于应用程序服务器和插件系统尤其有用,因为它允许组件使用自己的类...

    深入理解Java类加载.docx

    此外,还可以自定义类加载器,比如线程上下文类加载器,用于满足特定加载需求。 在多线程环境下,类的初始化是线程安全的。当多个线程尝试初始化同一类时,只有一个线程会执行()方法,其他线程会被阻塞。这确保了类...

    classloader-playground, 一个简单的java依赖隔离容器类.zip

    线程上下文类加载器主要用于解决应用程序类加载问题,尤其是在服务提供者接口(SPI)中,允许用户自定义扩展的加载。 总的来说,"classloader-playground"是一个实践和研究Java类加载机制的实用工具。通过这个项目...

    Understanding the Java ClassLoader.pdf

    然而,在多线程环境中,还需要注意线程上下文类加载器的正确设置,以避免类加载异常。 ### 适应Java 2的更新 为了使自定义类加载器兼容Java 2,开发者需要确保遵循双亲委派模型。这意味着在自定义类加载器中,应该...

    探索JVM底层奥秘ClassLoader源码分析与案例讲解

    7. **线程上下文类加载器**:在多线程环境下,每个线程都关联有一个线程上下文类加载器,用于服务线程特有的类加载需求。 8. **类的卸载**:Java中类一旦被加载就很难被卸载,因为垃圾回收器不会回收Class对象,...

    web中的路径问题

    - `Thread.currentThread().getContextClassLoader().getResource("/relative/path")`:获取当前线程上下文类加载器下的相对路径。 - 这种方式也适用于获取类路径下的资源。 - **通过系统属性获取路径**: - `...

    Java高并发编程详解:多线程与架构设计 (Java核心技术系列)

    本部分不仅详细解释了类加载的各个阶段,还探讨了线程上下文类加载器对于实现动态类加载、热部署等高级功能的作用。此外,与线程安全相关的数据一致性问题也在此部分得到了充分的讨论,为读者展示了在Java中如何通过...

    JVM基础原理

    2. **线程上下文类加载器**:每个线程都有自己的类加载器,通常用于加载应用程序的类。如果没有显式设置,将使用系统类加载器。 3. **网格类加载模型**:常见于OSGi等模块化框架,不同模块之间的类加载独立且有明确...

Global site tag (gtag.js) - Google Analytics