虽然双亲委派模型是 Java 类加载机制的推荐实现方式,但在某些特定场景下,为了实现特定的功能或满足特定的需求,需要打破双亲委派模型。以下是一些常见的需要打破双亲委派模型的场景:
1. SPI (Service Provider Interface) 机制:
-
问题: Java 中的 SPI 机制(例如 JDBC、JNDI、JAXP、日志框架等)允许第三方提供服务的实现。
- SPI 的核心接口通常由启动类加载器(Bootstrap Class Loader)加载(例如
java.sql.Driver接口)。 - SPI 的具体实现类通常由应用程序类加载器(Application Class Loader)或自定义类加载器加载(例如 MySQL、PostgreSQL 的 JDBC 驱动)。
- 如果严格遵循双亲委派模型,启动类加载器无法加载应用程序类加载器或自定义类加载器中的实现类,导致 SPI 机制无法正常工作。
- SPI 的核心接口通常由启动类加载器(Bootstrap Class Loader)加载(例如
-
解决方案: 线程上下文类加载器 (Thread Context ClassLoader)。
- Java 提供了
Thread.currentThread().getContextClassLoader()方法来获取当前线程的上下文类加载器。 - 如果没有设置,线程上下文类加载器默认是应用程序类加载器。
- SPI 的核心接口(例如
java.sql.DriverManager)通常会使用线程上下文类加载器来加载具体的实现类。
// java.sql.DriverManager 中的 loadInitialDrivers 方法 (简化版) private static void loadInitialDrivers() { // ... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 使用线程上下文类加载器 Iterator<Driver> driversIterator = loadedDrivers.iterator(); // ... } - Java 提供了
-
示例:
- JDBC:
java.sql.Driver接口由启动类加载器加载,但具体的 JDBC 驱动(例如 MySQL、PostgreSQL 的驱动)由应用程序类加载器加载。 - JNDI:
javax.naming.Context接口由启动类加载器加载,但具体的 JNDI 实现(例如文件系统、LDAP)由应用程序类加载器或自定义类加载器加载。 - 日志框架: SLF4J (Simple Logging Facade for Java) API 由启动类加载器加载, 但具体的日志实现 (例如 Logback, Log4j2) 由应用程序类加载器加载.
- JDBC:
2. OSGi (Open Service Gateway initiative):
- 问题: OSGi 是一个模块化系统,每个模块(bundle)都有自己的类加载器。如果严格遵循双亲委派模型,模块之间无法共享类,也无法实现模块的热部署和更新。
- 解决方案: OSGi 使用了一种更复杂的类加载器模型,它不是严格的双亲委派模型。
- 每个 bundle 都有自己的类加载器。
- bundle 可以显式地声明它们依赖的其他 bundle,以及它们导出的包。
- OSGi 框架会根据这些声明来解析 bundle 之间的依赖关系,并加载相应的类。
- OSGi 允许 bundle 动态地安装、启动、停止、更新和卸载,而无需重启整个应用程序。
3. 热部署 (HotSwap) / 热更新 (Hot Update):
- 问题: 在应用程序运行时,动态地替换或更新类,而无需重启应用程序。如果严格遵循双亲委派模型,无法替换已经被加载的类。
- 解决方案:
- 自定义类加载器: 创建一个新的类加载器来加载新版本的类,并替换旧的类加载器。
- 卸载旧的类加载器: 要卸载旧的类加载器, 需要确保没有任何对象引用旧的类加载器加载的类。
- 注意: 热部署/热更新需要非常小心,可能会导致类版本冲突、内存泄漏等问题。
- 示例:
- Java Instrumentation API 可以用来实现热部署.
- 一些 IDE(例如 IntelliJ IDEA、Eclipse)支持热部署功能。
- JRebel 等商业工具可以实现更高级的热部署功能。
4. Tomcat 等 Web 容器:
- 问题:
- 隔离性: Web 容器需要部署多个 Web 应用程序,每个应用程序可能使用不同版本的类库。如果严格遵循双亲委派模型,不同的 Web 应用程序可能会相互干扰。
- 共享性: Web 容器需要加载一些所有 Web 应用程序都可以访问的共享类库(例如,Servlet API、JDBC 驱动)。
- 解决方案:
- Tomcat 使用多个类加载器来实现 Web 应用程序的隔离和共享库的加载。
- Tomcat 的类加载器结构:
- Bootstrap: 加载 JVM 启动所需的类。
- System: 加载 Tomcat 自身的类。
- Common: 加载 Tomcat 和所有 Web 应用程序都可以访问的类(例如,JDBC 驱动)。
- WebAppX: 每个 Web 应用程序都有一个独立的
WebAppClassLoader,负责加载该应用程序的类(位于WEB-INF/classes和WEB-INF/lib目录下)。 - Shared: (可选) 存放所有 web 应用共享的类。
- 加载顺序:
BootstrapSystemCommonWebAppX(先在WEB-INF/classes中查找,再在WEB-INF/lib中查找,最后才委托给父类加载器Common)。Shared(如果配置了)
- 打破双亲委派:
WebAppClassLoader会优先在自己的范围内查找类,而不是立即委托给父类加载器,这打破了双亲委派模型。
5. 代码隔离:
- 场景: 希望在同一个 JVM 中运行多个版本的同一个类库,或者希望不同的应用程序使用不同版本的类库。
- 解决方案: 使用自定义类加载器,为每个应用程序或每个版本的类库创建一个独立的类加载器。
- 示例:
- 一个 JVM 中运行多个不同版本的应用程序.
- 一个应用需要同时使用两个不同版本的同一个第三方库.
6. 字节码加密/解密:
- 场景: 为了保护代码,可以对
.class文件进行加密。 - 解决方案: 使用自定义类加载器, 在加载类之前先对字节码进行解密。
总结:
需要打破双亲委派模型的场景通常是为了实现以下目标:
- 加载特定版本的类: 例如,SPI 机制需要加载服务提供者的实现类,热部署需要加载新版本的类。
- 隔离不同的应用程序或模块: 例如,OSGi 和 Tomcat 需要隔离不同的模块或 Web 应用程序。
- 实现特殊的功能: 例如,从网络或数据库加载类,对类进行加密解密。
打破双亲委派模型需要谨慎,因为它可能会带来一些风险(例如,类冲突、安全性问题)。 需要根据具体的需求选择合适的方案,并确保正确地处理类加载的各种细节。
3293

被折叠的 条评论
为什么被折叠?



