双亲委派 & URLClassLoader & SPI

本文介绍了Java中的ClassLoader和双亲委派机制,详细讲解了URLClassLoader的工作原理,包括loadClass和findClass方法。接着探讨了SPI(Service Provider Interface)服务发现机制,解释了为何BootstrapClassLoader和ExtClassLoader无法加载MySQL驱动,并通过ServiceLoader的使用展示了SPI的实现原理。文章最后提到了ServiceLoader的使用步骤和SPI的应用实例。
  • ClassLoader & 双亲委派
  • URLClassLoader
  • SPI(Service Provider Interface)

ClassLoader & 双亲委派

ClassLoader 编程语言 加载内容 Parent ClassLoader
BootstrapClassLoader C++ jre/lib;jre/classes Null Java虚拟机启动后初始化
ExtClassLoader Java jre/lib/ext;java.ext.dirs BootstrapClassLoader
AppClassLoader Java classpath指定位置 ExtClassLoader ClassLoader.getSystemClassLoader 返回值

ClassLoader的基本模型:

Java默认ClassLoader委派模型
载录:https://www.cnblogs.com/doit8791/p/5820037.html

URLClassLoader

  • ClassLoader.loadClass
  • URLClassLoader.findClass
  • ExtClassLoader & AppClassLoader 的URLClassPath

ClassLoader.loadClass

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
   
   
        synchronized (getClassLoadingLock(name)) {
   
   
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
   
   
                try {
   
   
                    if (parent != null) {
   
   
                        c = parent.loadClass(name, false);
                    } else {
   
   
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
   
   
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
   
   
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
   
    resolveClass(c); }
            return c;
        }
    }

    // ClassLoader method
    protected Class<?> findClass(String name) throws ClassNotFoundException {
   
   
        throw new ClassNotFoundException(name);
    }

其中findClass是关键:一来没有具体实现,二来修饰符是protected,所以 很明显是为了“开闭”。

URLClassLoader.findClass

// URLClassLoader overwrite
    protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
   
   
        final Class<?> result;
        try {
   
   
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
   
   
                    public Class<?> run() throws ClassNotFoundException {
   
   
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false)
&lt;think&gt;嗯,用户这次的问题相当专业,聚焦在JDK8和JDK9的类加载器架构差异,以及双亲委派模型的实践应用上。从提问深度来看,用户应该是有一定Java底层知识的开发者,可能正在处理模块化迁移或Tomcat优化相关的工作。 用户特别强调要对比JDK8和JDK9,这需要我精确区分两个版本的关键变化。引用[2]和[3]提到JDK9的模块化系统是分水岭:平台类加载器取代扩展加载器、所有内置加载器继承BuiltinClassLoader、新增模块化加载逻辑。这些在架构图中必须突出呈现。 关于双亲委派模型的用途,用户似乎更关注实践意义而非理论。除了基础的安全隔离和类唯一性,要补充Tomcat的实战案例(如webapp隔离)来说明&ldquo;打破&rdquo;的必要性。引用[1]提到的类相等性判断也值得展开&mdash;&mdash;这在OSGi动态加载场景至关重要。 Tomcat部分需要结构化说明:它的层级设计(Common/Webapp隔离)、并行加载优化、热部署实现原理,都要关联到双亲委派的突破点。尤其要强调WebappClassLoader如何通过重写loadClass()实现应用隔离,这点用户可能在调优时实际遇到过。 最后生成的问题要引导向深度实践:SPI机制和JDK9的模块化冲突是个经典坑点,自定义类加载器实现则是高级开发必备技能。用户既然问到Tomcat,可能对中间件设计也感兴趣。&lt;/think&gt;以下是针对JDK8与JDK9类加载器架构、双亲委派模型对比及其应用场景的详细分析: --- ### 一、JDK8与JDK9类加载器架构对比 | **特性** | **JDK8** | **JDK9(引入模块化)** | |------------------|-----------------------------------|-------------------------------------| | **顶层加载器** | Bootstrap ClassLoader | Bootstrap ClassLoader | | **扩展加载器** | Extension ClassLoader | **平台类加载器(Platform ClassLoader)** | | **应用加载器** | Application ClassLoader | Application ClassLoader | | **继承关系** | 各加载器独立继承URLClassLoader | 所有内置加载器继承`jdk.internal.loader.BuiltinClassLoader`[^3] | | **核心变化** | 基于JAR包和类路径加载 | **模块化加载**:按模块依赖关系加载类 | &gt; **关键差异**:JDK9用**平台类加载器**替代扩展加载器,并重构为模块化架构。所有内置加载器统一继承`BuiltinClassLoader`,实现模块化隔离和资源访问控制[^3]。 --- ### 二、双亲委派模型的工作流程(JDK8 vs JDK9) #### **JDK8 经典流程**: 1. 子加载器收到请求 &rarr; 委派父加载器 2. 父加载器尝试加载(失败则向下传递) 3. 最终由子加载器调用`findClass()`加载 &gt; **特点**:严格自顶向下委派(Bootstrap&rarr;Extension&rarr;Application) #### **JDK9 模块化流程**: 1. 加载请求 &rarr; **先搜索当前加载器的具名模块**(如平台类加载器检查模块路径) 2. 若模块中未找到 &rarr; 委派父加载器 3. 父加载器失败 &rarr; 子加载器尝试加载 &gt; **变化**:**优先模块内加载**,打破严格的自顶向下链条[^2]。 --- ### 三、双亲委派模型的四大核心用途 1. **避免类重复加载** 高层加载器(如Bootstrap)加载的类(如`java.lang.String`)不会被低层重复加载,确保类唯一性。 2. **保障核心库安全** 防止用户伪造核心类(如自定义`java.lang.Object`被高层加载器拦截)[^1]。 3. **隔离类冲突** 不同加载器加载的同名类(如`com.example.Foo`)被JVM视为不同类,避免冲突[^1]。 4. **资源高效利用** 按优先级委派减少冗余搜索,提升加载效率。 --- ### 四、打破双亲委派的场景与方法 #### **常见场景**: 1. **SPI机制(如JDBC驱动加载)** - **问题**:Bootstrap加载器无法加载`java.sql.Driver`实现类(位于应用路径) - **方案**:使用线程上下文类加载器(`ThreadContextClassLoader`)反向委派[^2]。 2. **热部署/热加载(如Tomcat)** - **需求**:独立加载每个Web应用的类,支持应用级隔离和重启。 3. **模块化动态加载(如OSGi)** - **需求**:允许不同模块使用同名类的独立版本。 #### **打破方法**: ```java // 自定义类加载器重写loadClass()打破委派 public class CustomClassLoader extends ClassLoader { @Override protected Class&lt;?&gt; loadClass(String name, boolean resolve) { // 1. 优先自己加载(跳过委派父类) Class&lt;?&gt; c = findLoadedClass(name); if (c == null) { c = findClass(name); // 直接加载目标类 } return c; } } ``` --- ### 五、Tomcat打破双亲委派的原因与优势 #### **原因**: 1. **应用隔离需求** 多个Web应用可能依赖**同名但版本冲突**的库(如Spring 4.x vs 5.x)。 2. **独立热部署** 单个应用重启时,需卸载其所有类而不影响其他应用。 3. **安全性隔离** 防止恶意Web应用篡改核心类或访问其他应用数据。 #### **实现方案**: - **自定义WebappClassLoader**:每个Web应用独享一个类加载器。 - **层级设计**: ```mermaid graph TD Bootstrap --&gt; Common[Common ClassLoader] Common --&gt; Catalina[Server ClassLoader] Common --&gt; Shared[Shared ClassLoader] Shared --&gt; Webapp1[WebappClassLoader A] Shared --&gt; Webapp2[WebappClassLoader B] ``` - **Common**:加载Tomcat核心库(所有Webapp共享) - **WebappClassLoader**:优先加载`/WEB-INF/classes`和`/WEB-INF/lib`中的应用私有类**(不委派父加载器)** #### **优势**: - ✅ **类隔离**:应用A的`lib/foo.jar v1.0`与应用B的`lib/foo.jar v2.0`互不影响。 - ✅ **热部署**:重启应用时,丢弃其专属`WebappClassLoader`并新建实例。 - ✅ **资源高效**:共享核心库(如Servlet API)仅由Common加载一次。 --- ### 总结 | **维度** | **JDK8** | **JDK9** | **Tomcat实践** | |---------------|-----------------------|------------------------|------------------------| | **架构核心** | 类路径委派 | 模块化优先加载 | 应用级类加载器隔离 | | **安全性** | 核心库保护 | 模块访问控制 | 防止跨应用类篡改 | | **灵活性** | SPI需打破委派 | 内置模块化支持 | 热部署无需重启容器 | | **典型场景** | 传统JAR应用 | 模块化项目 | 多租户Web容器 | &gt; 双亲委派是Java安全的基石,但在模块化、隔离性需求下,合理打破它才能实现更高灵活性[^2][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值