收藏 分享(赏)

线程上下文类加载器.doc

上传人:hwpkd79526 文档编号:7077470 上传时间:2019-05-05 格式:DOC 页数:5 大小:46.50KB
下载 相关 举报
线程上下文类加载器.doc_第1页
第1页 / 共5页
线程上下文类加载器.doc_第2页
第2页 / 共5页
线程上下文类加载器.doc_第3页
第3页 / 共5页
线程上下文类加载器.doc_第4页
第4页 / 共5页
线程上下文类加载器.doc_第5页
第5页 / 共5页
亲,该文档总共5页,全部预览完了,如果喜欢就下载吧!
资源描述

1、线程上下文类加载器问题:何时使用 Thread.getContextClassLoader()?这是一个很常见的问题,但答案却很难回答。这个问题通常在需要动态加载类和资源的系统编程时会遇到。总的说来动态加载资源时,往往需要从三种类加载器里选择:系统或说程序的类加载器、当前类加载器、以及当前线程的上下文类加载器。在程序中应该使用何种类加载器呢?系统类加载器通常不会使用。此类加载器处理启动应用程序时 classpath 指定的类,可以通过 ClassLoader.getSystemClassLoader()来获得。所有的 ClassLoader.getSystemXXX()接口也是通过这个类加载器

2、加载的。一般不要显式调用这些方法,应该让其他类加载器代理到系统类加载器上。由于系统类加载器是 JVM 最后创建的类加载器,这样代码只会适应于简单命令行启动的程序。一旦代码移植到 EJB、Web 应用或者 Java Web Start 应用程序中,程序肯定不能正确执行。因此一般只有两种选择,当前类加载器和线程上下文类加载器。当前类加载器是指当前方法所在类的加载器。这个类加载器是运行时类解析使用的加载器,Class.forName(String) 和Class.getResource(String)也使用该类加载器。代码中 X.class 的写法使用的类加载器也是这个类加载器。线程上下文类加载器在

3、 Java 2(J2SE)时引入。每个线程都有一个关联的上下文类加载器。如果你使用 new Thread()方式生成新的线程,新线程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系统类加载器作为上下文类加载器。Web 应用和 Java 企业级应用中,应用服务器经常要使用复杂的类加载器结构来实现 JNDI(Java 命名和目录接口)、线程池、组件热部署等功能,因此理解这一点尤其重要。为什么要引入线程的上下文类加载器?将它引入 J2SE 并不是纯粹的噱头,由于 Sun 没有提供充分的文档解释说明这一点,这使许多开发者很糊涂。实际上,上下文类加

4、载器为同样在 J2SE 中引入的类加载代理机制提供了后门。通常 JVM 中的类加载器是按照层次结构组织的,目的是每个类加载器(除了启动整个 JVM 的原初类加载器)都有一个父类加载器。当类加载请求到来时,类加载器通常首先将请求代理给父类加载器。只有当父类加载器失败后,它才试图按照自己的算法查找并定义当前类。有时这种模式并不能总是奏效。这通常发生在 JVM 核心代码必须动态加载由应用程序动态提供的资源时。拿 JNDI 为例,它的核心是由 JRE 核心类(rt.jar)实现的。但这些核心JNDI 类必须能加载由第三方厂商提供的 JNDI 实现。这种情况下调用父类加载器(原初类加载器)来加载只有其子

5、类加载器可见的类,这种代理机制就会失效。解决办法就是让核心 JNDI 类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆着代理机制的方向使用类加载器。顺便提一下,XML 解析 API(JAXP)也是使用此种机制。当 JAXP 还是 J2SE 扩展时,XML 解析器使用当前累加载器方法来加载解析器实现。但当 JAXP 成为 J2SE 核心代码后,类加载机制就换成了使用线程上下文加载器,这和 JNDI 的原因相似。好了,现在我们明白了问题的关键:这两种选择不可能适应所有情况。一些人认为线程上下文类加载器应成为新的标准。但这在不同 JVM 线程共享数据来沟通时,就会使类加载器的结构乱七八糟

6、。除非所有线程都使用同一个上下文类加载器。而且,使用当前类加载器已成为缺省规则,它们广泛应用在类声明、Class.forName 等情景中。即使你想尽可能只使用上下文类加载器,总是有这样那样的代码不是你所能控制的。这些代码都使用代理到当前类加载器的模式。混杂使用代理模式是很危险的。更为糟糕的是,某些应用服务器将当前类加载器和上下文类加器分别设置成不同的ClassLoader 实例。虽然它们拥有相同的类路径,但是它们之间并不存在父子代理关系。想想这为什么可怕:记住加载并定义某个类的类加载器是虚拟机内部标识该类的组成部分,如果当前类加载器加载类 X 并接着执行它,如 JNDI 查找类型为 Y 的数

7、据,上下文类加载器能够加载并定义 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 序列化

8、 API 缺省使用调用者当前的类加载器这些类加载器非常混乱,没有在 J2SE 文档中给以清晰明确的说明。该如何选择类加载器?如若代码是限于某些特定框架,这些框架有着特定加载规则,则不要做任何改动,让框架开发者来保证其工作(比如应用服务器提供商,尽管他们并不能总是做对)。如在Web 应用和 EJB 中,要使用 Class.gerResource 来加载资源。在其他情况下,需要考虑使用下面的代码,这是作者本人在工作中发现的经验:public abstract class ClassLoaderResolver/* This method selects the best classloader i

9、nstance to be used for* class/resource loading by whoever calls this method. The decision* typically involves choosing between the callers 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 #se

10、tStrategy.* 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 sta

11、tic 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 Securit

12、yManager* 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 SecurityManagerprotected Class getClassContext ()return super.getClassContext (); / End of nested

13、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 private static final int

14、 CALL_CONTEXT_OFFSET = 3; / may need to change if this class is redesignedprivate static final CallerResolver CALLER_RESOLVER; / set in statictry/ This can fail if the current SecurityManager does not allow/ RuntimePermission (“createSecurityManager“):CALLER_RESOLVER = new CallerResolver ();catch (S

15、ecurityException se)throw new RuntimeException (“ClassLoaderResolver: could not create CallerResolver: “ + se);s_strategy = new DefaultClassLoadStrategy (); / End of class.可通过调用 ClassLoaderResolver.getClassLoader()方法来获取类加载器对象,并使用其ClassLoader 的接口来加载类和资源。此外还可使用下面的 ResourceLoader 接口来取代ClassLoader 接口:pu

16、blic abstract class ResourceLoader/* see java.lang.ClassLoader#loadClass(java.lang.String)*/public static Class loadClass (final String name)throws ClassNotFoundExceptionfinal ClassLoader loader = ClassLoaderResolver.getClassLoader (1);return Class.forName (name, false, loader);/* see java.lang.Clas

17、sLoader#getResource(java.lang.String)*/ public static URL getResource (final String name)final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);if (loader != null)return loader.getResource (name);elsereturn ClassLoader.getSystemResource (name);. more methods . / End of class决定应该使用何种类加载器的接

18、口是 IClassLoaderStrategy:public interface IClassLoadStrategyClassLoader getClassLoader (ClassLoadContext ctx); / End of interface为了帮助 IClassLoadStrategy 做决定,给它传递了个 ClassLoadContext 对象作为参数:public class ClassLoadContextpublic final Class getCallerClass ()return m_caller;ClassLoadContext (final Class ca

19、ller)m_caller = caller;private final Class m_caller; / End of classClassLoadContext.getCallerClass()返回的类在 ClassLoaderResolver 或 ResourceLoader 使用,这样做的目的是让其能找到调用类的类加载器(上下文加载器总是能通过Thread.currentThread().getContextClassLoader()来获得)。注意调用类是静态获得的,因此这个接口不需现有业务方法增加额外的 Class 参数,而且也适合于静态方法和类初始化代码。具体使用时,可以往这个上

20、下文对象中添加具体部署环境中所需的其他属性。上面代码看起来很像 Strategy 设计模式,其思想是将“总是使用上下文类加载器”或者“总是使用当前类加载器” 的决策同具体实现逻辑分离开。往往设计之初是很难预测何种类加载策略是合适的,该设计能够让你可以后来修改类加载策略。这儿有一个缺省实现,应该可以适应大部分工作场景:public class DefaultClassLoadStrategy implements IClassLoadStrategypublic ClassLoader getClassLoader (final ClassLoadContext ctx)final ClassL

21、oader 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

22、)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 ();/

23、Precaution for when deployed as a bootstrap or extension class:if (isChild (result, systemLoader)result = systemLoader;return result;. more methods . / End of class上面代码的逻辑很简单:如调用类的当前类加载器和上下文类加载器是父子关系,则总是选择子类加载器。对子类加载器可见的资源通常是对父类可见资源的超集,因此如果每个开发者都遵循 J2SE 的代理规则,这样做大多数情况下是合适的。当前类加载器和上下文类加载器是兄弟关系时,决定使用哪

24、一个是比较困难的。理想情况下,Java 运行时不应产生这种模糊。但一旦发生,上面代码选择上下文类加载器。这是作者本人的实际经验,绝大多数情况下应该能正常工作。你可以修改这部分代码来适应具体需要。一般来说,上下文类加载器要比当前类加载器更适合于框架编程,而当前类加载器则更适合于业务逻辑编程。最后需要检查一下,以便保证所选类加载器不是系统类加载器的父亲,在开发标准扩展类库时这通常是个好习惯。注意作者故意没有检查要加载资源或类的名称。Java XML API 成为 J2SE 核心的历程应该能让我们清楚过滤类名并不是好想法。作者也没有试图检查哪个类加载器加载首先成功,而是检查类加载器的父子关系,这是更好更有保证的方法。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报