类加载器与双亲委派模型:一场家庭派对的规矩与叛逆(打破双亲委派的JDBC/Tomcat)

类加载器:家庭中的"接货员"

想象Java的类加载器(ClassLoader)是一个大家庭里的"接货员",专门负责把货物(.class文件)从仓库(磁盘、JAR包、网络等)搬到家里(JVM的内存)。这个家庭有好几代人:

  • Bootstrap ClassLoader (老祖宗):负责核心货物,比如java.lang.Object,住在/jre/lib里,用C++写的,沉默寡言但地位崇高。
  • Extension ClassLoader (爷爷辈):管一些扩展货物,比如/jre/lib/ext里的东西。
  • Application ClassLoader (父母辈):普通人家,负责你自己写的代码(classpath里的类)。
  • 自定义类加载器(叛逆的小辈):你自己DIY的接货员,想怎么搬货就怎么搬货。

这些"接货员"各司其职,但他们有个家规------双亲委派模型

双亲委派模型:家里的"向上问责制"

双亲委派模型就像一场家庭派对的规矩:如果小辈接到一个任务(加载一个类),不能擅自行动,必须先问爸妈(Application ClassLoader),爸妈再问爷爷(Extension ClassLoader),爷爷再问老祖宗(Bootstrap ClassLoader)。老祖宗如果能搞定(比如核心类),就直接干活;如果搞不定,就一级级往下推,直到有人能干为止。

用代码看一下这个过程(简化版):

java 复制代码
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 先问"老祖宗"和其他长辈(双亲委派)
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                c = getParent().loadClass(name); // 委托给父加载器
            } catch (ClassNotFoundException e) {
                // 父加载器搞不定,自己干
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

这个规矩的好处是:

  1. 避免重复劳动:同一个类不会被加载多次,省力。
  2. 安全第一 :核心类(比如java.lang.String)永远由老祖宗加载,防止小辈搞乱(比如伪造一个恶意的String类)。

比喻一下:就像家里有个珍贵花瓶(核心类),只能由老祖宗亲自搬运,小辈别想碰,免得摔碎了。

如何打破双亲委派模型:叛逆的小辈出马

但有时候,这个规矩太死板,小辈想自己干怎么办?打破双亲委派模型就是让小辈直接接手任务,不问长辈。

方法一:重写loadClass方法 默认的loadClass方法实现了双亲委派,我们可以直接改掉它:

java 复制代码
public class RebelClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 不问长辈,自己干
        return findClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 假设从某个特殊路径加载类
        byte[] classData = getClassData(name); // 自定义加载逻辑
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] getClassData(String name) {
        // 这里是自定义加载类的字节码,比如从网络或加密文件读取
        return new byte[0]; // 占位
    }
}

比喻:这就像小辈偷偷跑出去买了个新花瓶,不告诉爸妈,直接摆在客厅。

方法二:使用线程上下文类加载器 JVM提供了一个"后门"------Thread.setContextClassLoader(),可以绕过双亲委派。比如SPI(Service Provider Interface)机制里,java.sql.DriverManager会用上下文类加载器加载数据库驱动,而不是严格走双亲委派。

比喻:这像是小辈借了邻居家的搬运车(上下文类加载器),直接把货物搬进来,爸妈根本不知道。

Tomcat为什么要打破双亲委派:派对上的独立小团体

Tomcat是个Web容器,里面跑着多个Web应用(WAR包),每个应用就像一个小团体,有自己的"接货员"。如果都按双亲委派走,问题就来了:

  1. 冲突 :两个应用用了同一个类(比如com.example.MyUtil),但版本不同,怎么办?
  2. 隔离:应用之间得互不干扰,就像派对上的小团体不能抢别人的酒。

Tomcat的解决方案是重写类加载器,搞了个WebappClassLoader。它不完全遵守双亲委派,而是优先加载Web应用自己的类(WEB-INF/libWEB-INF/classes),加载不到再问长辈。

比喻:Tomcat就像派对的主持人,给每个小团体发了个独立的小仓库(WebappClassLoader),让他们先用自己的酒(类),不够了再去大酒窖(父加载器)拿。这样既避免了抢酒(类冲突),又保证了各自的口味(隔离性)。

代码大概是这样的(伪代码):

java 复制代码
public class WebappClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 先查自己的小仓库
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                c = findClass(name); // 优先加载WEB-INF下的类
            } catch (ClassNotFoundException e) {
                // 自己搞不定,再问长辈
                c = getParent().loadClass(name);
            }
        }
        return c;
    }
}

其他打破双亲委派的方式:更多叛逆玩法

  1. OSGi框架:每个Bundle(模块)有自己的类加载器,互不干扰,像派对上的独立VIP包厢。
  2. 热部署/动态加载:比如开发工具里的热加载,直接用自定义类加载器加载新类,像小辈半夜偷偷换了客厅的装饰。
  3. 插件化系统:像Eclipse插件或Android的插件化框架,每个插件有自己的类加载器,互不干涉,如同派对上的流动摊贩,各卖各的。

总结:规矩与叛逆的平衡

双亲委派模型是JVM的"家规",像老祖宗定下的秩序,稳妥又安全。但在复杂的派对(应用场景)中,Tomcat这样的"叛逆者"打破规矩,用独立的小仓库(自定义类加载器)满足了隔离和灵活的需求。其他玩法(OSGi、热部署)则是更多小团体和摊贩的创新。

记住这个比喻:双亲委派是老祖宗管家,Tomcat是派对上的小团体领队,叛逆的小辈们各有各的招儿,最终让这场Java派对既热闹又有序!

相关推荐
Moment20 分钟前
💯 铜三铁四,我收集整理了这些大厂面试场景题 (一)
前端·后端·面试
无名之逆1 小时前
轻量级、高性能的 Rust HTTP 服务器库 —— Hyperlane
服务器·开发语言·前端·后端·http·rust
无名之逆1 小时前
探索Hyperlane:用Rust打造轻量级、高性能的Web后端框架
服务器·开发语言·前端·后端·算法·rust
穆骊瑶1 小时前
Java语言的WebSocket
开发语言·后端·golang
追逐时光者2 小时前
精选 5 款基于 .NET 开源、功能强大的编辑器
后端·.net
uhakadotcom2 小时前
阿里云 MaxCompute SQLML:轻松实现机器学习
后端·面试·github
Asthenia04122 小时前
[4-Consumer]消费者端实现心跳功能
后端
Asthenia04122 小时前
[3-Consumer]回答面试官关于 MQ 项目中 Topic+Tag 二级消息过滤的思路整理
后端
Asthenia04122 小时前
[2-Consumer]如何回答面试官关于 MQ 轮子项目中 Push 和 Pull 混合消费的实现思路
后端
Asthenia04123 小时前
在面试中我被问到RocketMQ的延时队列是如何实现的。谈谈回答的思路
后端