JVM 如何打破双亲委派模型?

虽然双亲委派模型是 Java 类加载机制的推荐实现方式,但在某些情况下,为了实现特定的功能,可能需要打破双亲委派模型。以下是一些常见的打破双亲委派模型的方法和场景:

1. 重写 loadClass 方法 (不推荐):

  • 原理: java.lang.ClassLoaderloadClass 方法实现了双亲委派模型的逻辑。默认情况下,loadClass 方法会先检查类是否已经被加载过,如果没有,则委托给父类加载器加载。如果父类加载器无法加载,则调用 findClass 方法尝试自己加载。

  • 打破方式: 重写 loadClass 方法,不遵循双亲委派模型的逻辑,而是直接调用 findClass 方法尝试自己加载类,或者自定义加载逻辑。

  • 示例:

    public class MyClassLoader extends ClassLoader {
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            // 不再委托给父类加载器,直接尝试自己加载
            return findClass(name); 
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 实现自定义的类加载逻辑
            // ...
        }
    }
    
  • 缺点:

    • 破坏了双亲委派模型的优点: 可能会导致类的重复加载、核心类库被篡改等问题。
    • 容易出错: 需要自己处理类加载的各种细节,容易出错。
    • 不推荐: 除非有非常特殊的需求,否则不建议重写 loadClass 方法。

2. 使用线程上下文类加载器 (Thread Context ClassLoader):

  • 原理:
    • 每个线程都有一个关联的上下文类加载器 (Context ClassLoader)。
    • 可以通过 Thread.currentThread().getContextClassLoader() 获取当前线程的上下文类加载器。
    • 可以通过 Thread.currentThread().setContextClassLoader(ClassLoader cl) 设置当前线程的上下文类加载器。
    • 如果没有设置,线程上下文类加载器默认是应用程序类加载器 (AppClassLoader)。
  • 打破方式:
    • 在需要打破双亲委派模型的地方,获取当前线程的上下文类加载器,并使用它来加载类。
    • 例如,JDBC 驱动程序通常由应用程序类加载器加载,但 JDBC API (如 java.sql.DriverManager) 需要能够加载这些驱动程序。DriverManager 会使用线程上下文类加载器来加载驱动程序。
  • 应用场景:
    • SPI (Service Provider Interface): Java 中的 SPI 机制(例如 JDBC、JNDI、JAXP 等)通常使用线程上下文类加载器来加载服务提供者的实现类。
    • 框架和容器: 一些框架和容器(例如,Tomcat、Spring)会使用线程上下文类加载器来加载应用程序的类或资源。
  • 示例:
       // 获取当前线程的上下文类加载器
       ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

       // 使用上下文类加载器加载类
       try {
           Class<?> myClass = contextClassLoader.loadClass("com.example.MyClass");
           // ...
       } catch (ClassNotFoundException e) {
           // ...
       }

3. 自定义类加载器并重写 findClass 方法 (推荐):

  • 原理:
    • 继承 java.lang.ClassLoader,并重写 findClass 方法。
    • findClass 方法中实现自己的类加载逻辑,例如从特定的目录、URL 或数据库中加载类。
  • 优点:
    • 可以灵活地控制类的加载过程。
    • 可以实现一些特殊的功能,例如热部署、模块化等。
  • 示例: (前面已经提供过,此处不再重复)

4. OSGi (Open Service Gateway initiative):

  • 原理:
    • OSGi 是一个模块化系统,每个模块(bundle)都有自己的类加载器。
    • OSGi 的类加载器模型不是严格的双亲委派模型,而是更复杂的网状结构。
    • OSGi 允许模块显式地声明它们依赖的其他模块,以及它们导出的包。
    • OSGi 框架会根据这些声明来解析模块之间的依赖关系,并加载相应的类。
  • 打破方式: OSGi 的类加载器模型本身就不是双亲委派模型。

5. Tomcat 的类加载器:

  • 原理:
    • Tomcat 为了实现 Web 应用程序之间的隔离,以及共享库的加载,使用了多个类加载器。
    • Tomcat 的类加载器结构:
      • Bootstrap: 加载 JVM 启动所需的类。
      • System: 加载 Tomcat 自身的类。
      • Common: 加载 Tomcat 和所有 Web 应用程序都可以访问的类 (例如, JDBC 驱动)。
      • WebappX: 每个 Web 应用程序都有一个独立的 WebAppClassLoader,负责加载该应用程序的类 (位于 WEB-INF/classesWEB-INF/lib 目录下).
      • Shared: (可选) 存放所有web应用共享的类.
  • 加载顺序:
    1. Bootstrap
    2. System
    3. Common
    4. WebappX (先在 WEB-INF/classes 中查找, 再在 WEB-INF/lib 中查找,最后才委托给父类加载器).
    5. Shared (如果配置了的话)
  • 打破双亲委派:
    • WebAppClassLoader 会优先在自己的范围内查找类,而不是立即委托给父类加载器, 这打破了双亲委派模型。
    • 这样做的好处是:
      • 隔离性: 不同的 Web 应用程序可以使用不同版本的类库,而不会相互干扰。
      • 灵活性: Web 应用程序可以覆盖 Tomcat 或共享库提供的类。

总结:

打破双亲委派模型的主要方法有:

  • 重写 loadClass 方法 (不推荐)。
  • 使用线程上下文类加载器 (常用于 SPI 机制)。
  • 自定义类加载器并重写 findClass 方法 (推荐)。
  • OSGi 模块化系统 (使用自定义的类加载器模型)。
  • Tomcat 的类加载机制.

你可能感兴趣的:(JVM,jvm)