vlambda博客
学习文章列表

双亲委派模型的破坏者-线程上下文类加载器

2020/12/30 周韵洁


皇鼎科技

今日简单聊聊

双亲委派模型的破坏者-

线程上下文类加载器

文章有点长需耐心读完







 粗略介绍下自己 

 我叫周韵洁

是来自广东的一个女生

 在互联网也算是摸爬滚打了几年 

今日有幸与在座各位分享一下这篇文章 

 属个人见解望能帮助到各位朋友


The article is a personal opinion

 and I hope to help you.


线程上下文类加载器

就是数组列表,是集和类型

 Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

   这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。启动类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能委派给系统类加载器,因为它是系统类加载器的祖先类加载器。


在Java应用中存在着很多服务提供者接口

(Service Provider Interface,SPI)

双亲委派模型的破坏者-线程上下文类加载器

这些接口允许第三方为他们提供实现,如常见的SPI有JDBC、JNDI等,这些SPI的接口属于Java核心库,一般存在rt.jar包中,由bootstrap类加载器加载,而SPI的第三方实现代码则是作为Java应用所依赖的jar包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由Bootstrap类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派机制的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

线程上下文类加载器(contextClassLoader)是从JDK1.2开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初试线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例。


双亲委派模型的破坏者-线程上下文类加载器


如图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。为了进一步证实这种场景,不妨看看DriverManager类的源码,DriverManager是Java核心rt.jar包中的类,该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver,这里主要看看如何加载外部实现类,在DriverManager初始化时会执行如下代码

双亲委派模型的破坏者-线程上下文类加载器

在DriverManager类初始化时执行了loadInitialDrivers()方法,在该方法中通过ServiceLoader.load(Driver.class);去加载外部实现的驱动类,ServiceLoader类会去读取mysql的jdbc.jar下META-INF文件的内容,如下所示:

加载meta-inf过程:
实现延迟服务提供者查找
DriverManager.loadInitialDrivers -> ServiceLoader.load -> reload ->
lookupIterator = new LazyInterator(service, loader);
加载meta-inf,初始化驱动
loadedDrivers.iterator() -> driversIterator.hasNext() -> hasNextService ->
ClassLoader.getSystemResources(fullName);

这样ServiceLoader会帮我们处理一切,并最终通过load()方法加载

核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的,通过这种方式实现了Java核心代码内部去调用外部实现类。

简而言之就是ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader或者ExtClassLoader等),在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。


好了,这一期的分享会就到这里结束了

感谢大家的阅读,如有不足的地方请大家大大关照。