基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java SPI 机制是如何破坏双亲委派模型的?

知识点图片

Java SPI(Service Provider Interface)机制破坏双亲委派模型,主要是因为核心类库(由顶层类加载器加载)需要去加载和实例化由第三方提供的具体实现类(由底层类加载器加载),这违背了双亲委派模型中“子类加载器向上委托”的单向依赖原则。

为了让你清晰地理解这个过程,我们分步骤来剖析:

1. 回顾:什么是正常的“双亲委派模型”?

在正常的双亲委派模型中,类加载的顺序是自下而上的(向上委托):

  • Application ClassLoader(应用类加载器)遇到类加载请求,委托给父加载器。
  • Extension ClassLoader(扩展类加载器)继续向上委托。
  • Bootstrap ClassLoader(启动类加载器)尝试加载。如果加载不到,再逐层向下交由子加载器尝试加载。

核心规则:父类加载器对子类加载器是不可见的。也就是说,启动类加载器只能加载核心类(如 rt.jar),它无法去调用或加载位于应用 Classpath 下的用户类。

2. 冲突产生:SPI 面临的困境

SPI 的核心思想是:Java 官方定义接口,第三方厂商提供实现。最典型的例子就是 JDBC

  • 接口在哪里? java.sql.Driver 接口存在于 Java 核心类库 rt.jar 中。它是由 Bootstrap ClassLoader(启动类加载器) 加载的。
  • 实现类在哪里? MySQL 提供的实现类 com.mysql.cj.jdbc.Driver 存在于用户引入的第三方 jar 包中。它应该由 Application ClassLoader(应用类加载器) 加载。

困境出现了:
当我们在代码中调用 DriverManager.getConnection() 时,DriverManager 类(由 Bootstrap 加载)需要去寻找并实例化所有实现了 Driver 接口的第三方类。
根据类加载机制的默认规则:一个类加载其引用的其他类时,会默认使用自身的类加载器
这就意味着,DriverManager 会尝试用 Bootstrap ClassLoader 去加载 MySQL 的驱动类。但是,Bootstrap ClassLoader 根本找不到存放在 Classpath 下的 MySQL 驱动包!

3. 如何破坏(解决思路):线程上下文类加载器 (TCCL)

既然父加载器没法向下找子加载器帮忙,Java 的设计者就引入了一个“作弊”工具:线程上下文类加载器(Thread Context ClassLoader,简称 TCCL)

这个加载器可以通过 Thread.currentThread().setContextClassLoader() 来设置。如果在创建线程时没有设置,它会从父线程继承;如果在应用程序的全局范围都没有设置过的话,它默认就是 Application ClassLoader(应用类加载器)

4. 完整的破坏过程(以 ServiceLoader 为例)

Java 提供了 java.util.ServiceLoader 来实现 SPI 机制。当 DriverManagerServiceLoader 需要加载第三方实现时,它是这样做的:

  1. ServiceLoader 是核心类,由 Bootstrap ClassLoader 加载。
  2. ServiceLoader.load(Driver.class) 被调用时,它实际上并没有使用 Bootstrap ClassLoader 去加载实现类。
  3. 相反,它去获取了当前线程的上下文类加载器:Thread.currentThread().getContextClassLoader()(这里拿到的通常是 Application ClassLoader)。
  4. ServiceLoader 拿着这个底层获取到的 AppClassLoader,去扫描 META-INF/services/ 目录,并把第三方厂商的实现类加载进来。

5. 为什么说这是一种“破坏”?

因为在这个过程中,启动类加载器(父)实际上委托了应用类加载器(子)去加载类,完成了一次“逆向委托”(自顶向下)

这就打破了双亲委派模型中严格的“自底向上委托,父加载器无法访问子加载器可见类”的原则。

总结

Java SPI 破坏双亲委派模型,本质上是基础框架(如 JDBC)需要调用用户代码(如 MySQL 驱动)时引发的类加载器可见性问题。通过引入线程上下文类加载器(TCCL),允许父类加载器“借用”子类加载器来加载类,从而巧妙(且刻意)地打破了双亲委派的限制。这种“破坏”并不是设计的失误,而是为了实现灵活的插件化扩展而必须做出的妥协。

00:00
00:00