关键在于“谁加载什么、为什么不能混、出问题时往哪查”三点:Tomcat按Web应用分层,本地优先且打破双亲委派保障隔离;OSGi以Bundle为单位,通过Import/Export显式控制类可见性,支持热更新与多版本共存。

理解 Tomcat 或 OSGi 中的类加载器隔离架构,关键不是记住几层加载器的名字,而是抓住“谁加载什么、为什么不能混、出问题时往哪查”这三点。它们都打破了标准双亲委派,但打破的方式和目标完全不同。
Tomcat:按部署单元分层,本地优先保障应用独立
Tomcat 把每个 Web 应用当作一个隔离单位,类加载围绕 /WEB-INF/classes 和 /WEB-INF/lib 展开:
- WebappClassLoader 是每个应用专属的,启动时自动创建,生命周期与应用绑定
- 它先查自己目录(classes → lib),查不到才逐级向上委托给 Shared → Common → System → Bootstrap
- 但有硬性例外:所有 java.*、javax.*(除 javax.servlet.* 等容器 API)、org.apache.catalina.* 类,一律跳过本地查找,直交父加载器
- Servlet 接口等由 CommonClassLoader 加载,确保所有应用看到同一份定义;而 Spring、MyBatis 这类业务依赖则由各自 WebappClassLoader 加载,互不干扰
OSGi:按模块契约控制可见性,显式声明代替隐式委托
OSGi 不靠父子链,而是用 Bundle(模块)+ 清单文件(MANIFEST.MF)构建平级协作关系:
- 每个 Bundle 拥有独立 ClassLoader,不自动向上委托,也不默认共享任何类
- 要用别的模块的类?必须在 Import-Package 里写明,且对方已在 Export-Package 中导出
- 同一接口多个实现可共存(比如两个 Bundle 各提供一个 DataSourceFactory),通过服务注册中心按属性匹配调用
- Bundle 可动态 stop/uninstall,其 ClassLoader 被丢弃,已加载类可被 GC 回收,真正支持热更新
验证你看到的到底是谁在加载类
别猜,写两行代码就能定位:
立即学习“Java免费学习笔记(深入)”;
- getClass().getClassLoader() —— 当前 Servlet 所属的 WebappClassLoader(Tomcat)或 BundleClassLoader(OSGi)
- getClass().getClassLoader().getParent() —— 在 Tomcat 中通常是 Shared 或 Common;在 OSGi 中可能是框架级加载器(如 FrameworkClassLoader)
- String.class.getClassLoader() —— 输出 null,说明由 Bootstrap 加载,这是 JVM 底层保证
- 在 WEB-INF/lib 放一个自定义 jar,确认能否被本应用加载、其他应用却报 ClassNotFoundException
设计背后的刚性需求
两类架构都不是为了炫技,而是解决三类真实问题:
- 类隔离:Spring 4 和 Spring 5 同时跑在一个服务器上,不能互相污染
- 热更新:改完 JSP 或 Bundle,不用重启整个容器就能生效
- 跨层级服务加载:比如 JDBC 驱动由应用提供,但 DriverManager 却在 Bootstrap 里——这时就得靠线程上下文类加载器(Thread.currentThread().setContextClassLoader())临时切换加载能力


















