Spring Boot 类加载流程分析

 更新时间:2026年03月11日 10:35:57   作者:d3y1  
本文给大家介绍Spring Boot 类加载流程分析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Java 类加载基础

类加载器层次结构

Java 虚拟机使用双亲委派模型(Parent Delegation Model)来加载类:

Bootstrap ClassLoader (启动类加载器)
    ↓
Extension/Platform ClassLoader (扩展/平台类加载器)
    ↓
Application/System ClassLoader (应用/系统类加载器)

双亲委派机制

双亲委派模型的工作原理:

  1. 当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类
  2. 而是把这个请求委派给父类加载器去完成
  3. 每一个层次的类加载器都是如此
  4. 只有当父加载器反馈自己无法完成这个加载请求(找不到所需的类)时,子加载器才会尝试自己去加载
protected Class<?> loadClass(String name, boolean resolve) {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    // 2. 委派给父类加载器
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载器无法加载
            }
            if (c == null) {
                // 3. 自己尝试加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

Spring Boot 类加载架构

为什么需要特殊的类加载器?

Spring Boot 应用通常打包为可执行的 JAR(Fat JAR 或 Uber JAR),这种 JAR 包含了:

  • 应用代码
  • 所有依赖的第三方库
  • Spring Boot 自身的类

传统的 JAR 加载机制无法正确处理这种嵌套的 JAR 结构,因此 Spring Boot 实现了自定义的类加载器。

Spring Boot 启动流程

java -jar app.jar
    ↓
JarLauncher.main()
    ↓
创建 LaunchedURLClassLoader
    ↓
设置 ContextClassLoader
    ↓
调用应用程序的 Main 方法

核心类

Spring Boot 类加载相关的核心类位于 org.springframework.boot.loader 包:

类名作用
JarLauncher可执行 JAR 的启动器
WarLauncher可执行 WAR 的启动器
LaunchedURLClassLoaderSpring Boot 自定义的类加载器
JarFile封装 JAR 文件访问
JarFileRegisterJAR 文件注册表

Fat JAR 结构

目录结构

Spring Boot 可执行 JAR 的内部结构:

app.jar
├── BOOT-INF/
│   ├── classes/           # 应用类文件
│   │   └── com/example/
│   │       └── Application.class
│   └── lib/               # 依赖库
│       ├── spring-boot-2.7.0.jar
│       ├── spring-core-5.3.20.jar
│       └── ...
├── META-INF/
│   ├── MANIFEST.MF        # 清单文件
│   └── spring.factories   # Spring 配置
├── org/springframework/boot/loader/  # Spring Boot Loader 类
│   ├── JarLauncher.class
│   ├── LaunchedURLClassLoader.class
│   └── ...
└── [其他资源]

MANIFEST.MF 关键配置

Manifest-Version: 1.0
Start-Class: com.example.Application
Main-Class: org.springframework.boot.loader.JarLauncher
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.7.0

关键属性说明:

  • Main-Class: 指向 Spring Boot 的启动器类
  • Start-Class: 实际的应用程序主类
  • Spring-Boot-Classes: 应用类所在目录
  • Spring-Boot-Lib: 依赖库所在目录

LaunchedURLClassLoader 详解

继承关系

java.lang.ClassLoader
    ↓
java.net.URLClassLoader
    ↓
org.springframework.boot.loader.LaunchedURLClassLoader

核心特性

1. 打破双亲委派

LaunchedURLClassLoader 在某些情况下会打破传统的双亲委派模型:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 1. 检查是否已经加载
    Class<?> loadedClass = findLoadedClass(name);
    if (loadedClass == null) {
        // 2. 对于某些包,优先从 BOOT-INF 加载
        if (isEligibleForOverride(name)) {
            try {
                loadedClass = findClass(name);
                if (resolve) {
                    resolveClass(loadedClass);
                }
                return loadedClass;
            } catch (ClassNotFoundException e) {
                // 继续尝试父加载器
            }
        }
        // 3. 使用标准的双亲委派
        return super.loadClass(name, resolve);
    }
    return loadedClass;
}

2. URL 处理

LaunchedURLClassLoader 能够处理嵌套 JAR 的 URL:

jar:file:/path/to/app.jar!/BOOT-INF/lib/spring-core-5.3.20.jar!/

这种 URL 格式表示:

  • 外层 JAR: file:/path/to/app.jar
  • 内层 JAR: BOOT-INF/lib/spring-core-5.3.20.jar

3. 资源加载

@Override
public URL findResource(String name) {
    // 优先从 BOOT-INF/classes 查找
    URL url = findResourceInBootInf(name);
    if (url != null) {
        return url;
    }
    // 然后从 BOOT-INF/lib 查找
    return findResourceInDependencies(name);
}

类加载优先级

Spring Boot 类加载器的加载优先级:

Bootstrap ClassLoader (JDK 核心类)
LaunchedURLClassLoader (BOOT-INF/classes)
LaunchedURLClassLoader (BOOT-INF/lib)
Extension/Platform ClassLoader
Application ClassLoader (其他)

类加载委托模型

Spring Boot 的委托策略

Spring Boot 并非完全打破双亲委派,而是在特定场景下进行调整:

正常情况(遵循双亲委派):
Application ClassLoader → LaunchedURLClassLoader → Bootstrap ClassLoader
特殊情况(打破双亲委派):
对于 Spring Boot 应用类,LaunchedURLClassLoader 优先加载

加载决策流程

收到类加载请求
    ↓
是否是 JDK 核心类?
    ↓ 是 → Bootstrap ClassLoader 加载
    ↓ 否
是否是 Spring Boot Loader 类?
    ↓ 是 → Bootstrap ClassLoader 加载
    ↓ 否
是否在 BOOT-INF/classes 中?
    ↓ 是 → LaunchedURLClassLoader 加载
    ↓ 否
是否在 BOOT-INF/lib 中?
    ↓ 是 → LaunchedURLClassLoader 加载
    ↓ 否
委托给父类加载器

类隔离

Spring Boot 的类加载机制实现了类隔离:

  • 应用类BOOT-INF/classes/
  • 依赖类BOOT-INF/lib/
  • 系统类rt.jar, jce.jar

这种隔离确保了:

  1. 应用类不会被系统类覆盖
  2. 不同版本的依赖可以共存
  3. 类加载冲突最小化

常见问题与解决方案

1. ClassNotFoundException

问题现象:

java.lang.ClassNotFoundException: com.example.MyClass

可能原因:

  • 类文件不在 BOOT-INF/classes/
  • 依赖 JAR 不在 BOOT-INF/lib/
  • MANIFEST.MF 配置错误

解决方案:

<!-- Maven 确保正确打包 -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

2. NoClassDefFoundError

问题现象:

java.lang.NoClassDefFoundError: org/springframework/core/ResolvableTypeProvider

可能原因:

  • 依赖版本冲突
  • 类加载顺序问题
  • 传递依赖未正确包含

解决方案:

<!-- 排除冲突依赖 -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>some-lib</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. ClassCastException

问题现象:

java.lang.ClassCastException: com.example.MyClass cannot be cast to com.example.MyClass

可能原因:

  • 同一个类被不同的类加载器加载
  • 类加载器隔离导致类型不匹配

解决方案:

// 确保使用正确的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");

4. NoSuchMethodError

问题现象:

java.lang.NoSuchMethodError: org.springframework.util.StringUtils.isEmpty(Ljava/lang/String;)Z

可能原因:

  • 依赖版本不匹配
  • 方法签名在不同版本中发生变化

解决方案:

<!-- 使用 dependencyManagement 统一版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>5.3.20</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

5. 资源文件找不到

问题现象:

InputStream is = getClass().getResourceAsStream("/config.properties"); // 返回 null

可能原因:

  • 资源文件打包位置不正确
  • 使用了错误的类加载器

解决方案:

// 使用正确的加载方式
InputStream is = Thread.currentThread()
    .getContextClassLoader()
    .getResourceAsStream("config.properties");
// 或者使用 ClassPathResource
Resource resource = new ClassPathResource("config.properties");
InputStream is = resource.getInputStream();

最佳实践

1. 依赖管理

<!-- 使用 Spring Boot BOM 统一版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.7.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2. 避免类冲突

<!-- 排除不需要的传递依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. 自定义类加载器

public class CustomClassLoader extends LaunchedURLClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        // 自定义加载逻辑
        if (name.startsWith("com.custom.")) {
            Class<?> clazz = findLoadedClass(name);
            if (clazz == null) {
                clazz = findClass(name);
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }
        return super.loadClass(name, resolve);
    }
}

4. 资源加载最佳实践

@Service
public class ResourceService {
    // 推荐:使用 Resource 抽象
    @Value("classpath:config.properties")
    private Resource configResource;
    public void loadConfig() throws IOException {
        Properties props = new Properties();
        try (InputStream is = configResource.getInputStream()) {
            props.load(is);
        }
    }
    // 推荐:使用 ClassPathResource
    public Resource loadResource(String path) {
        return new ClassPathResource(path);
    }
}

5. 调试类加载问题

@Component
public class ClassLoaderDebugger implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        System.out.println("Context ClassLoader: " + classLoader);
        System.out.println("Parent ClassLoader: " + classLoader.getParent());
        // 打印类路径
        if (classLoader instanceof URLClassLoader) {
            URL[] urls = ((URLClassLoader) classLoader).getURLs();
            System.out.println("Classpath URLs:");
            for (URL url : urls) {
                System.out.println("  " + url);
            }
        }
    }
}

6. JVM 参数配置

# 启用类加载详细日志
java -verbose:class -jar app.jar
# 设置类加载器调试
java -Djava.security.debug=classloader -jar app.jar
# 指定自定义类加载器
java -Djava.system.class.loader=com.example.CustomClassLoader -jar app.jar

总结

Spring Boot 的类加载机制是其可执行 JAR 功能的核心:

  1. LaunchedURLClassLoader 是核心类加载器,能够处理嵌套 JAR 结构
  2. Fat JAR 结构 将应用类和依赖类组织在 BOOT-INF 目录下
  3. 类加载策略 在保持双亲委派的同时,针对 Spring Boot 应用进行了优化
  4. 类隔离 确保了应用类和依赖类的正确加载顺序

理解 Spring Boot 的类加载机制有助于:

  • 解决类加载相关的各种问题
  • 优化应用启动性能
  • 实现更复杂的应用架构
  • 进行有效的故障排查

到此这篇关于Spring Boot 类加载流程分析的文章就介绍到这了,更多相关springboot类加载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Javaee多线程之进程和线程之间的区别和联系(最新整理)

    Javaee多线程之进程和线程之间的区别和联系(最新整理)

    进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,run为方法,start启动线程,实现并发执行,本文给大家介绍Javaee多线程之进程和线程之间的区别和联系,感兴趣的朋友跟随小编一起看看吧
    2025-07-07
  • Java线程池7个参数的详细含义

    Java线程池7个参数的详细含义

    java多线程开发时,常常用到线程池技术,这篇文章是对创建java线程池时的七个参数的详细解释,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Java 实战范例之员工管理系统的实现

    Java 实战范例之员工管理系统的实现

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+vue+Springboot+ssm+mysql+maven+redis实现一个前后端分离的员工管理系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Spring Bean创建和循环依赖

    Spring Bean创建和循环依赖

    这篇文章主要介绍了Spring Bean创建和循环依赖,讲述了Spring容器中 Bean 的创建过程已经主要的方法,另外也着重分析了循环依赖的问题,需要的小伙伴可以参考一下
    2022-05-05
  • Java中StringBuilder类常用方法总结

    Java中StringBuilder类常用方法总结

    这篇文章主要介绍了Java中StringBuilder类常用方法的相关资料,StringBuilder类是Java中用于频繁修改字符串的可变字符串缓冲区类,它提供了多种方法进行字符串操作,如添加、插入、删除、替换字符等,需要的朋友可以参考下
    2024-12-12
  • Python爬虫之爬取2020女团选秀数据

    Python爬虫之爬取2020女团选秀数据

    本文将对比《青春有你2》和《创造营2020》全体小姐姐,鉴于两个节目的数据采集和处理过程基本相似,在使用Python做数据爬虫采集的章节中将只以《创造营2020》为例做详细介绍。感兴趣的同学可以照猫画虎去实操一下《青春有你2》的数据爬虫采集,需要的朋友可以参考下
    2021-04-04
  • Jackson 反序列化时实现大小写不敏感设置

    Jackson 反序列化时实现大小写不敏感设置

    这篇文章主要介绍了Jackson 反序列化时实现大小写不敏感设置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java使用itext导出PDF文本绝对定位(实现方法)

    java使用itext导出PDF文本绝对定位(实现方法)

    下面小编就为大家带来一篇java使用itext导出PDF文本绝对定位(实现方法)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Springboot下使用Redis管道(pipeline)进行批量操作

    Springboot下使用Redis管道(pipeline)进行批量操作

    本文主要介绍了Spring boot 下使用Redis管道(pipeline)进行批量操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • SpringBoot使用CORS解决无法跨域访问问题的具体步骤

    SpringBoot使用CORS解决无法跨域访问问题的具体步骤

    跨域问题指的是不同站点之间,使用 ajax 无法相互调用的问题,跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据,本文给大家介绍了SpringBoot使用CORS解决无法跨域访问问题的具体步骤,需要的朋友可以参考下
    2025-05-05

最新评论