可以打破双亲委派机制吗


1.什么是双亲委派模型

双亲委派模型(Parents Delegation Model)默认的类加载逻辑是:一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。

双亲委派机制的优点:

  • 可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证全局唯一性

  • Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一

  • 保护程序安全,防止类库的核心 API 被随意篡改

    例如:在工程中新建 java.lang 包,接着在该包下新建 String 类,并定义 main 函数

    public class String {
        public static void main(String[] args) {
            System.out.println("demo info");
        }
    }

    此时执行 main 函数会出现异常,在类 java.lang.String 中找不到 main 方法。因为JVM的启动类加载器 Bootstrap ClassLoader会优先加载 java.lang 包下的原生 String 类,而用户自定义的String类会被忽略。


2.怎样打破双亲委派模型

双亲委派机制的缺点:检查类是否加载的委托过程是单向的,这个方式虽然从结构上看比较清晰,使各个 ClassLoader 的职责非常明确,但顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类(可见性)

双亲委派模型并不是一个具有强制性约束的模型,而是 Java 设计者推荐给开发者的类加载器实现方式

破坏双亲委派模型的方式:

  • 自定义 ClassLoader

    • 如果不想破坏双亲委派模型,只需要重写 findClass 方法
    • 如果想要去破坏双亲委派模型,需要去**重写 loadClass **方法
    • 双亲委派的逻辑在loadClass方法中,所以必须重写loadClass才能破坏机制,而findClass只是加载类的一种方式,不涉及委派逻辑。因此,正确的做法是覆盖loadClass,跳过父类加载器的调用,直接加载类。
  • 引入线程上下文类加载器

    Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:

    • SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的
    • SPI 的实现类是由系统类加载器加载,引导类加载器是无法找到 SPI 的实现类,因为双亲委派模型中 BootstrapClassloader 无法委派 AppClassLoader 来加载类

    JDK 开发人员引入了线程上下文类加载器(Thread Context ClassLoader),这种类加载器可以通过 Thread 类的 setContextClassLoader 方法进行设置线程上下文类加载器,在执行线程中抛弃双亲委派加载模式,使程序可以逆向使用类加载器,使 Bootstrap 加载器拿到了 Application 加载器加载的类,破坏了双亲委派模型

  • 实现程序的动态性,如代码热替换(Hot Swap)、模块热部署(Hot Deployment)

    IBM 公司主导的 JSR一291(OSGiR4.2)实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(OSGi 中称为 Bundle)都有一个自己的类加载器,当更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换,在 OSGi 环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构

    当收到类加载请求时,OSGi 将按照下面的顺序进行类搜索:

    1. 将以 java.* 开头的类,委派给父类加载器加载
    2. 否则,将委派列表名单内的类,委派给父类加载器加载
    3. 否则,将 Import 列表中的类,委派给 Export 这个类的 Bundle 的类加载器加载
    4. 否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载
    5. 否则,查找类是否在自己的 Fragment Bundle 中,如果在就委派给 Fragment Bundle 类加载器加载
    6. 否则,查找 Dynamic Import 列表的 Bundle,委派给对应 Bundle 的类加载器加载
    7. 否则,类查找失败

    热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为,热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。

注意:对于java核心类,还是要走双亲委派机制的,只有那些自己想打破双亲委派机制的类,采取走自定义的加载方法。


3.打破双亲委派模型的风险

  • 安全性:可能加载未经授权或恶意的代码,破坏应用程序的安全性,
  • 兼容性问题:自定义类加载器与标准Java库可能存在兼容性问题
  • 维护负担:复杂的类加载逻辑需要额外的代码维护和管理

使用建议:只有在特定需求下才考虑打破这一机制,如实现自定义插件系统或支持模块化运行时功能。在构建严肃的应用程序时,务必仔细考虑潜在的安全风险和兼容性问题。


文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录