---
### **一、双亲委派模型的核心逻辑**
双亲委派模型是 Java 类加载器(ClassLoader)的默认工作流程,其核心规则为:
1. **加载优先级**:子类加载器(如 `AppClassLoader`)在加载类时,优先委派父类加载器(如 `ExtClassLoader`)处理。
2. **避免重复加载**:父类加载器无法加载的类,才由子类加载器自行加载。
3. **安全隔离**:核心类(如 `java.lang` 包)由启动类加载器(`BootstrapClassLoader`)加载,防止恶意代码篡改。
#### **类加载器层级**:
| **类加载器** | **加载路径** | **责任** |
|--------------------------|---------------------------------------|-----------------------------------|
| `BootstrapClassLoader` | `jre/lib` 下的核心库(如 `rt.jar`) | 加载 JDK 核心类(如 `java.*`) |
| `ExtClassLoader` | `jre/lib/ext` 下的扩展库 | 加载扩展类(如 `javax.*`) |
| `AppClassLoader` | 应用的 `-classpath` 或 `CLASSPATH` | 加载用户自定义类 |
---
### **二、SPI 与 JNDI 的类加载机制**
#### **1. SPI 的类加载器问题**
SPI 的接口(如 `java.sql.Driver`)由 `BootstrapClassLoader` 加载,但实现类(如 `com.mysql.jdbc.Driver`)通常位于应用路径下,需由 `AppClassLoader` 加载。此时双亲委派模型会导致 **父类加载器无法访问子类加载器的类**,从而无法加载 SPI 实现类。
**解决方案**:
• **线程上下文类加载器(Thread Context ClassLoader, TCCL)**
SPI 通过 `Thread.currentThread().getContextClassLoader()` 获取上下文类加载器(默认是 `AppClassLoader`),绕过双亲委派模型,直接加载实现类。
**示例(JDBC 驱动加载)**:
```java
// DriverManager 内部使用 TCCL 加载驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
```
#### **2. JNDI 的类加载器问题**
JNDI 的核心接口(如 `javax.naming.Context`)由 `BootstrapClassLoader` 加载,但其实现(如 `LDAP` 或 `RMI` 提供者)可能位于应用路径或外部库中,需由 `AppClassLoader` 加载。
**解决方案**:
• **同上使用 TCCL**
JNDI 通过 `Thread.currentThread().getContextClassLoader()` 加载具体实现类。
**示例(InitialContext 初始化)**:
```java
// JNDI 通过 TCCL 加载 SPI 实现(如文件系统或 LDAP 提供者)
Context ctx = new InitialContext();
```
---
### **三、类加载器调用的技术要点**
#### **1. 双亲委派模型的“破坏”**
• **主动绕过**:通过 `TCCL` 直接指定类加载器,使父类加载器(如 `BootstrapClassLoader`)能间接访问子类加载器(如 `AppClassLoader`)的类。
• **设计意义**:在保证核心类安全的前提下,允许动态扩展服务实现。
#### **2. TCCL 的设置**
• **默认值**:线程创建时继承父线程的 TCCL,通常为 `AppClassLoader`。
• **手动设置**:
```java
// 设置当前线程的上下文类加载器
Thread.currentThread().setContextClassLoader(customClassLoader);
```
#### **3. 模块化系统(Java 9+)的影响**
• **模块路径(Module Path)**:Java 9 引入模块化后,SPI/JNDI 的实现类需在模块描述文件(`module-info.java`)中声明 `provides` 和 `uses`,类加载器会通过模块层(Module Layer)加载,不再完全依赖 TCCL。
---
### **四、总结**
| **场景** | **类加载器** | **双亲委派模型处理** |
|------------------|----------------------------------|-----------------------------------|
| SPI 接口定义 | `BootstrapClassLoader` | 父类加载器加载核心接口 |
| SPI 实现类加载 | `AppClassLoader`(通过 TCCL) | 绕过双亲委派,直接加载实现类 |
| JNDI 接口定义 | `BootstrapClassLoader` | 父类加载器加载核心接口 |
| JNDI 实现类加载 | `AppClassLoader`(通过 TCCL) | 同上 |
**结论**:
SPI 和 JNDI 通过 **线程上下文类加载器(TCCL)** 绕过了双亲委派模型的限制,使父类加载器(如 `BootstrapClassLoader`)能间接加载子类加载器(如 `AppClassLoader`)路径下的服务实现类。这种机制既保留了双亲委派的安全性,又实现了服务的动态扩展能力。