打破双亲委派模型

2024 年 9 月 22 日 星期日(已编辑)
3
摘要
在 Java 中,双亲委派模型是默认的类加载机制,但在特定场景下可以打破双亲委派模型。这种情况通常出现在应用服务器/容器、OSGi模块化系统、JDBC驱动加载、热部署和插件系统等场景中。打破双亲委派模型可以实现类隔离、处理类库冲突或实现热部署的需求。
这篇文章上次修改于 2024 年 9 月 22 日 星期日,可能部分内容已经不适用,如有疑问可询问作者。

打破双亲委派模型

在 Java 中,双亲委派模型是默认的类加载机制,旨在保证类加载的安全性和一致性。

打破双亲委派模型,指允许自定义类加载器直接加载类,而不再优先委托给父类加载器。

打破双亲委派模型的主要场景通常出现在特殊框架(如应用服务器、插件系统)中,或者是为了实现热部署、模块隔离等高级功能。

1. 应用服务器或 Web 容器(如 Tomcat)

背景:

Java 的应用服务器(如 TomcatJetty)或 Java EE 容器通常需要运行多个 Web 应用(每个应用有自己独立的类库)。这些类库可能会包含和 Java 核心类库相同的类名或版本不同的类。

为什么需要打破双亲委派?

  • 假设 Web 应用中有一个 javax.servlet.Servlet 接口。在标准的双亲委派模型下,这个类会被父类加载器(系统类加载器)加载。如果不同版本的 Web 应用使用了不同的 Servlet 实现,父类加载器总是先加载同一个版本的类,可能导致类冲突。

  • 解决这个问题的方法是:每个 Web 应用都有自己的类加载器。通过自定义类加载器,Tomcat 可以直接为 Web 应用加载它自己的类,而不总是委托给父类加载器。这种方式实现了类隔离,允许不同应用使用不同版本的类。

示例:

Tomcat 中,每个 Web 应用都有自己的类加载器(WebappClassLoader)。当你部署一个 Web 应用时,Tomcat 会为这个应用创建一个独立的类加载器,确保应用的类不会与全局的类库产生冲突。

- Bootstrap 类加载器
    |
- System 类加载器
    |
- WebappClassLoader (为每个 Web 应用创建)

打破双亲委派的方式允许 Tomcat 的 Web 应用加载自己特定版本的类,而不是系统默认的类库。

2. OSGi(动态模块化系统)

背景:

OSGi 是 Java 中实现模块化的框架,它允许应用程序在运行时加载、卸载、更新模块(称为 bundle)。这些模块可以独立加载自己的类,并且不同模块之间可能会有版本冲突

为什么需要打破双亲委派?

  • 在 OSGi 中,不同的模块可能会依赖不同版本的类库。如果按照标准的双亲委派模型,父类加载器会优先加载类,导致所有模块共享同一个类库版本。这种方式不适合 OSGi 的场景,因为 OSGi 允许模块之间有不同的依赖版本。

  • OSGi 通过自定义的类加载机制打破了双亲委派模型,使得每个模块有自己独立的类加载器。这样,不同模块可以加载相同类的不同版本,解决了类版本冲突的问题。

示例:

在 OSGi 中,每个 bundle 有自己的类加载器,通过自定义的类加载器,可以直接加载模块内的类,而不必委托给父类加载器。

- Bootstrap 类加载器
    |
- OSGi Bundle 类加载器(为每个模块创建)

这种方式允许每个模块加载自己的依赖,而不受全局类库的影响。

3. JDBC Driver 加载

背景:

Java 应用程序通常需要使用数据库驱动程序(JDBC Driver)来连接数据库。JDBC 驱动程序是由第三方厂商提供的,通常放在应用的 classpath 中。

为什么需要打破双亲委派?

  • JDBC 驱动程序通常是由应用类加载器加载的,但 JDBC 连接的调用是在核心类库中(如 java.sql.DriverManager),这些类是由 Bootstrap 类加载器加载的。如果严格按照双亲委派模型,应用类加载器加载的 JDBC 驱动无法被核心类库直接使用。

  • 为了解决这个问题,JVM 中的 DriverManager 使用了一种线程上下文类加载器的机制,允许由应用类加载器加载的 JDBC 驱动被核心类库调用。这种机制实际上打破了双亲委派模型,让应用类加载器加载的类可以被 Bootstrap 类加载器加载的类使用。

示例:

Class.forName("com.mysql.jdbc.Driver");

在这种情况下,com.mysql.jdbc.Driver 是由应用类加载器加载的,而 java.sql.DriverManager 是由 Bootstrap 类加载器加载的。通过线程上下文类加载器,DriverManager 可以正确加载驱动并使用它。

4. 热部署/热加载

背景:

热部署(Hot Deployment)是指应用程序在不停止 JVM 的情况下,更新、重载某些类或模块。这在开发 Web 应用或其他需要快速迭代的场景中非常有用。

为什么需要打破双亲委派?

  • 在正常的双亲委派模型下,类加载器一旦加载了某个类,这个类就会一直驻留在内存中,除非 JVM 停止。如果需要在不关闭 JVM 的情况下更新某个类,必须打破双亲委派模型,使用自定义的类加载器来加载更新后的类。

  • 热部署系统通常通过为每个版本的应用创建新的类加载器,并卸载旧的类加载器来实现类的重新加载。新的类加载器可以加载最新版本的类,而旧的类加载器及其加载的类最终会被垃圾回收,达到热加载的效果。

示例:

Spring BootTomcat 等框架支持热部署,它们通过动态创建新的类加载器并将更新后的类加载到 JVM 中,从而实现应用的热更新。

- Bootstrap 类加载器
    |
- Application ClassLoader v1 (老版本类)
    |
- Application ClassLoader v2 (新版本类)

新的类加载器可以加载更新后的类,而旧的类加载器及其类将最终被回收。

5. 插件系统(如 Eclipse 插件)

背景:

插件系统允许应用程序通过加载外部插件来扩展功能。每个插件可能有自己独立的类库,甚至多个插件可能依赖同样名字的类但版本不同。

为什么需要打破双亲委派?

  • 在插件系统中,插件通常是动态加载的,它们的类库可能与主程序或其他插件的类库冲突。如果严格按照双亲委派模型,插件的类库会被父类加载器加载,导致不同插件共享同一个类库,可能引发版本不一致等问题。

  • 为了解决这个问题,插件系统通常为每个插件创建一个独立的类加载器,打破双亲委派模型,确保不同插件可以使用自己版本的类库。

示例:

Eclipse 的插件系统使用自定义类加载器为每个插件加载独立的类库。这种方式允许每个插件隔离运行,避免类库冲突。

- Bootstrap 类加载器
    |
- Plugin ClassLoader 1 (插件 A 类)
    |
- Plugin ClassLoader 2 (插件 B 类)

小结

打破双亲委派模型通常发生在需要类隔离、处理类库版本冲突或实现热部署的场景下。

常见的场景包括:

  1. 应用服务器/容器(如 Tomcat)中的类隔离。
  2. OSGi 模块化系统,不同模块加载不同版本的类。
  3. JDBC 驱动加载,通过线程上下文类加载器打破双亲委派。
  4. 热部署,通过自定义类加载器实现类的重载。
  5. 插件系统,为每个插件创建独立的类加载器,避免冲突。
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...