热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

Java的ThreadContext类加载器的实现

这篇文章主要介绍了Java的ThreadContext类加载器的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

疑惑

以前在看源码的时候,总是会遇到框架里的代码使用Thread.currentThread.getContextClassLoader()获取当前线程的Context类加载器,通过这个Context类加载器去加载类。

我们平时在程序中写代码的时候,遇到要动态加载类的时候,一般使用Class.forName()的方式加载我们需要的类。比如最常见的,当我们进行JDBC编程的时候,我们通过Class.forName()去加载JDBC的驱动。

try {
 return Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
 // skip
}

那么为什么当我们使用Class.forName()的方式去加载类的时候,如果类找不到,我们还要尝试用Thread.currentThread.getContextLoader()获取的类加载器去加载类呢?比如我们可能会碰到下面这种代码:

try {
 return Class.forName(className);
} catch (ClassNotFoundException e) {
 // skip
}

ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
if (ctxClassLoader != null) {
 try {
 clazz = ctxClassLoader.loadClass(className);
 } catch (ClassNotFoundException e) {
   // skip
 }
}

这里加粗的部分,就是使用了Thread.currentThread.getContextLoader()获取的加载器去加载类。显然,Class.forName()加载类的时候使用的类加载器可能和Thread.currentThread.getContextLoader()获取的类加载器是不同的。那么为什么会出现不同呢?

JAVA的类加载器

要理解为什么会用到Thread.currentThread.getContextLoader()获取的这个类加载器之前,我们先来了解下JVM里使用的类加载器(ClassLoader)。

JVM默认有三种类加载器:

  1. Bootstrap Class Loader
  2. Extension Class Loader
  3. System Class Loader

Bootstrap Class Loader

Bootstrap Class Loader类加载器是JDK自带的一款类加载器,用于加载JDK内部的类。Bootstrap类加载器用于加载JDK中$JAVA_HOME/jre/lib下面的那些类,比如rt.jar包里面的类。Bootstrap类加载器是JVM的一部分,一般采用native代码编写。

Extension Class Loader

  Extension Class Loader类加载器主要用于加载JDK扩展包里的类。一般$JAVA_HOME/lib/ext下面的包都是通过这个类加载器加载的,这个包下面的类基本上是以javax开头的。

System Class Loader

System Class Loader类加载器也叫应用程序类加载器(AppClassLoader)。顾名思义,这个类加载器就是用来加载开发人员自己平时写的应用代码的类的。System类加载器是用于加载存放在classpath路径下的那些应用程序级别的类的。

下面的代码列举出了这三个类加载器:

public class MainClass {
 public static void main(String[] args) {
  System.out.println(Integer.class.getClassLoader());
  System.out.println(Logging.class.getClassLoader());
  System.out.println(MainClass.class.getClassLoader());
 }
}

其中获取Bootstrap类加载器永远返回null值

null # Bootstrap类加载器
sun.misc.Launcher$ExtClassLoader@5e2de80c # Extension类加载器
sun.misc.Launcher$AppClassLoader@18b4aac2 # System类加载器

双亲委派模型

上面介绍的三种类加载器,并不是孤立的,他们之间有一个层次关系:

三个类加载器之间通过这个层次关系协同工作,一起负责类的加载工作。上面的这种层次模型称为类加载器的“双亲委派”模型。双亲委派模型要求,除了最顶层的Bootstrap类加载器之外,所有的类加载器都必须有一个parent加载器。当类加载器加载类的时候,首先检查缓存中是否有已经被加载的类。如果没有,则优先委托它的父加载器去加载这个类,父加载器执行和前面子加载器一样的工作,直到请求达到顶层的Bootstrap类加载器。如果父加载器不能加载需要的类,那么这个时候才会让子加载器自己去尝试加载这个类。工作原理类似于下面这种方式:

  

我们可以通过JDK里ClassLoader里面的代码一窥双亲委派机制的实现方式,代码实现在ClassLoader.loadClass()里面

rotected Class<&#63;> loadClass(String name, boolean resolve)
 throws ClassNotFoundException

 synchronized (getClassLoadingLock(name)) {
  // First, check if the class has already been loaded
  Class<&#63;> c = findLoadedClass(name);
  if (c == null) {
   long t0 = System.nanoTime();
   try {
    if (parent != null) {
     c = parent.loadClass(name, false);
    } else {
     c = findBootstrapClassOrNull(name);
    }
   } catch (ClassNotFoundException e) {
    // ClassNotFoundException thrown if class not found
    // from the non-null parent class loader
   }

   if (c == null) {
    // If still not found, then invoke findClass in order
    // to find the class.
    long t1 = System.nanoTime();
    c = findClass(name);

    // this is the defining class loader; record the stats
    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    sun.misc.PerfCounter.getFindClasses().increment();
   }
  }
  if (resolve) {
   resolveClass(c);
  }
  return c;
 }

采用双亲委派的方式组织类加载器,一个好处是为了安全。如果我们自己定义了一个String类,希望将这个String类替换掉默认Java中的java.lang.String的实现。

我们将自己实现的String类的class文件放到classpath路径下,当我们使用类加载器去加载我们实现的String类的时候,首先,类加载器会将请求委托给父加载器,通过层层委派,最终由Bootstrap类加载器加载rt.jar包里的String类型,然后一路返回给我们。在这个过程中,我们的类加载器忽略掉了我们放在classpath中自定义的String类。

如果没有采用双亲委派机制,那么System类加载器可以在classpath路径中找到String的class文件并加载到程序中,导致JDK中的String实现被覆盖。所以类加载器的这种工作方式,在一定程度上保证了Java程序可以安全稳定的运行。

线程上下文类加载器

上面讲了那么多类加载器相关的内容,可还是没有讲到今天的主题,线程上下文类加载器。

到这里,我们已经知道Java提供了三种类加载器,并且按照严格的双亲委派机制协同工作。表面上,似乎很完美,但正是这种严格的双亲委派机制导致在加载类的时候,存在一些局限性。

当我们更加基础的框架需要用到应用层面的类的时候,只有当这个类是在我们当前框架使用的类加载器可以加载的情况下我们才能用到这些类。换句话说,我们不能使用当前类加载器的子加载器加载的类。这个限制就是双亲委派机制导致的,因为类加载请求的委派是单向的。

虽然这种情况不多,但是还是会有这种需求。比较典型的,JNDI服务。JNDI提供了查询资源的接口,但是具体实现由不同的厂商实现。这个时候,JNDI的代码是由JVM的Bootstrap类加载器加载,但是具体的实现是用户提供的JDK之外的代码,所以只能由System类加载器或者其他用户自定义的类加载器去加载,在双亲委派的机制下,JNDI获取不到JNDI的SPI的实现。

为了解决这个问题,引入了线程上下文类加载器。通过java.lang.Thread类的setContextClassLoader()设置当前线程的上下文类加载器(如果没有设置,默认会从父线程中继承,如果程序没有设置过,则默认是System类加载器)。有了线程上下文类加载器,应用程序就可以通过java.lang.Thread.setContextClassLoader()将应用程序使用的类加载器传递给使用更顶层类加载器的代码。比如上面的JNDI服务,就可以利用这种方式获取到可以加载SPI实现的类加载器,获取需要的SPI实现类。

可以看到,引入线程类加载器实际是对双亲委派机制的破坏,但是却提供了类加载的灵活性。

解惑

回到开头,框架的代码为了加载框架之外用户实现的类,由于这些类可能没法通过框架使用的类加载器进行加载,为了绕过类加载器的双亲委派模型,采用Thread.getContextClassLoader()的方式去加载这些类。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 使用C#开发SQL Server存储过程的指南
    本文介绍如何利用C#在SQL Server中创建存储过程,涵盖背景、步骤和应用场景,旨在帮助开发者更好地理解和应用这一技术。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • 本文探讨了如何在发布 XenApp 应用时,通过命令行参数实现启动时的参数传递。特别介绍了静态和动态参数传递的方法,并详细解释了 ICA 文件中两种参数传递方式的区别及安全检查机制。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 尽管某些细分市场如WAN优化表现不佳,但全球运营商路由器和交换机市场持续增长。根据最新研究,该市场预计在2023年达到202亿美元的规模。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 题库来源:安全生产模拟考试一点通公众号小程序G3锅炉水处理报名考试是安全生产模拟考试一点通生成的,G3锅炉水处理证模拟考试题库是根据G3锅炉水处理最新 ... [详细]
  • 为了深入了解辽源市市民对智能城市建设的感受和建议,国家统计局辽源调查队近期开展了一项针对200位市民的问卷调查。结果显示,大部分市民对智能城市的认知度较高,并对其带来的便利表示肯定。 ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • 解决MongoDB Compass远程连接问题
    本文记录了在使用阿里云服务器部署MongoDB后,通过MongoDB Compass进行远程连接时遇到的问题及解决方案。详细介绍了从防火墙配置到安全组设置的各个步骤,帮助读者顺利解决问题。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
author-avatar
爱你真好958_358
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有