前言
在SpringBoot 中,类加载机制与Java的传统双亲委派类加载机制是有一定区别。主要体现在自定义类加载器 与**fat jar(可执行jar)**的加载方式上。
Java的传统双亲委派模型
Java传统类加载机制,遵循双亲委派模型,核心规则:类加载请求优先由父类加载器处理,只有父加载器无法加载时才由子加载器尝试。
1、JDK 1.8及更早版本采用如下层级结构:

2、从 JDK 9 引入模块系统开始,是这样的层级结构

这样设计的主要目的是为了,避免重复加载核心类(如java.lang.String),确保安全性(防止用户篡改核心类)。
SpringBoot的类加载器改造
改造原因
SpringBoot 通过自定义类加载器LaunchedURLClassLoader打破了传统双亲委派的严格层级,主要解决fat jar中嵌套jar的加载问题。
在SpringBoot 中,使用打包构建工具时,无论是Maven还是Gradle,在lib/目录中的第三方依赖是以JAR形式打入项目主JAR内的,默认会生成一个包含所有依赖项的fat jar。
目录结构示例如下:
            
            
              yaml
              
              
            
          
          mySpringBootApp.jar
├── BOOT-INF
│   ├── classes(用户代码)
│   └── lib(依赖的第三方jar)
└── org.springframework.boot.loader
        传统的Java类加载机制,Application ClassLoader只能从外部classpath加载类,无法直接加载JAR包内嵌的其他JAR(fat jar),因此SpringBoot加入了自定义的类加载器。
主要做了哪些改造
SpringBoot 使用LaunchedURLClassLoader(继承自URLClassLoader)替代了ApplicationClassLoader,通过运行时动态生成jar路径的URL来加载嵌套jar。
LaunchedURLClassLoader会先加载BOOT-INF/classes目录下的应用类(优先于JDK类)。- 再加载
BOOT-INF/lib/目录下的依赖JAR,LaunchedURLClassLoader会解析BOOT-INF/lib/下的每个jar,将其URL添加到类路径中。 - 最后再交给父类加载器(即
ApplicationClassLoader)。 
总结一下:为了加载嵌套在主JAR内部的fat jar,SpringBoot在类加载流程上做了改造,增加了LaunchedURLClassLoader类加载器,并且会先尝试加载自身的类和依赖JAR,找不到要加载的类时,才交给父类加载器,从而对传统的双亲委派模型进行了改造。
注意,LaunchedURLClassLoader 仅对 BOOT-INF/classes 和 BOOT-INF/lib 下的类采用"子加载器优先"策略,核心类库仍严格遵循双亲委派,因此不会破坏 JDK 的安全模型。
扩展知识
在使用SpringBoot 进行开发项目时,SpringBoot 官方推荐我们使用热部署的方式是使用 spring-boot-devtools 模块。
其实SpringBoot 的热部署并不是真正意义上的"热替换 ",而是通过 双类加载器机制 实现的"快速重启"。
SpringBoot 的"热部署"主要实现原理如下:
- 双 ClassLoader 架构 :
Base ClassLoader:加载第三方 jar 包(不会频繁变动)Restart ClassLoader:加载开发者自己写的类(会频繁变动)
 - 文件变化监听机制
DevTools启动一个后台线程,监听classpath下.class文件的变化- 一旦检测到变化,丢弃旧的 
Restart ClassLoader - 重新创建一个新的 
Restart ClassLoader,加载更新后的类 - 然后通过反射重新调用 
main()方法,实现应用重启 
 
由于不需要重新加载第三方类(Base ClassLoader 不变),也不需要重新初始化整个 Spring 容器,重启过程只涉及开发者代码部分,节省大量时间。
虽然叫"热部署 ",但本质上是"部分重启 ",不是真正的 JVM 热替换(如 JRebel 那样)
SpringBoot如何指定在其他Bean之前实例化指定的Bean
Bean 实例化/初始化顺序其实就是指"哪个 Bean 先被 new、哪个 @PostConstruct 先跑"。
目前有6种方式可以实现按照一定顺序进行实例化Bean。
1、构造器依赖(最稳,无侵入)
Spring 保证一个 Bean 实例化之前,它依赖的 Bean 必须已实例化。
直接让 BeanA 的构造器里需要 BeanB,或者 BeanA 里有一个非延迟的 BeanB 字段 + @Autowired。
如下代码
            
            
              java
              
              
            
          
          @Configuration
public class Config {
    @Bean
    public B b() { return new B(); }
    @Bean
    public A a(B b) {      // Spring 保证 b() 先跑
        return new A(b);
    }
}
        使用这种方式,理解起来简单,并且可靠性高,与具体的应用框架无关,但是也有一定的短板,就是按指定顺序实例化的Bean,必须存在真实的依赖关系。
2、@DependsOn(显式声明,无真正依赖也适用)
虽然两个 Bean 之间没有构造器/字段依赖,但你仍想让 BeanB 先实例化于BeanA。
这个时候就可以使用@DependsOn注解了,但是需要注意一点:@DependsOn 只能保证先实例化,不能保证先销毁(销毁顺序用 DependentBean.destroyMethod 或 DisposableBeanAdapter)。
@DependsOn 仅控制 初始化顺序;销毁时 Spring 会按依赖关系的反向顺序执行,因此若 B 依赖 A,则 B 先销毁,A 后销毁。
如下代码:
            
            
              java
              
              
            
          
          @Configuration
public class Config {
    @Bean
    public B b() { return new B(); }
    @Bean
    @DependsOn("b")   // 容器会先实例化 b,再实例化 a
    public A a() { return new A(); }
}
        直接将注解写在类上也可以
            
            
              java
              
              
            
          
          @Component
@DependsOn("b")
public class A { }
        3、@Order 或 Ordered(只影响"收集型"顺序)
适用范围:
@Bean方法返回的是 Collection 注入点(如List<X>、Map<String,X>)。CommandLineRunner / ApplicationRunner / Filter / Interceptor等"链式"扩展点。对普通的单例 Bean 实例化顺序无效。
即使两个单例 Bean 实现了 Ordered 接口,只要它们之间不存在"收集型注入"或"链式扩展点",Spring 仍然不保证谁先实例化。
使用场景如下代码示例,使用此注解的Bean都是实现了同一个接口的同类型。
            
            
              java
              
              
            
          
          @Component
@Order(1)
public class FirstRunner implements CommandLineRunner { ... }
@Component
@Order(2)
public class SecondRunner implements CommandLineRunner { ... }
        4、BeanDefinitionRegistryPostProcessor(BeanFactoryPostProcessor 的扩展)
在容器刷新早期(所有 BeanDefinition 加载完、实例化之前)把定义顺序调到自己想要的顺序。
            
            
              java
              
              
            
          
          @Component
public class OrderBeanProcessor implements BeanFactoryPostProcessor {
		// 让某个 Bean 在普通 Bean 实例化之前提前实例化
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        PrimaryOrderBean bean = beanFactory.getBean(PrimaryOrderBean.class);
        System.out.println(bean);
    }
}
@Component
public class PrimaryOrderBean {
    public PrimaryOrderBean() {
        System.out.println("init primary order bean");
    }
    @Override
    public String toString() {
        return "PrimaryOrderBean{toString}";
    }
}
        此方式的风险:可读性差,容易踩坑,除非写框架,否则不建议。
5、 @AutoConfigureBefore / @AutoConfigureAfter(仅对 spring.factories 里的自动配置生效)
当在写自己的 starter 时,想让 MyAutoConfiguration 在 DataSourceAutoConfiguration 之前/之后运行。
对普通 @Configuration 无效,也不会影响 Bean 实例化顺序,只影响配置类解析顺序。
            
            
              java
              
              
            
          
          @Configuration
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration { }
        这两个注解只对 META-INF/spring.factories 中注册的 EnableAutoConfiguration 类生效;写在普通 @Configuration 类上会被忽略。
6、实现 PriorityOrdered / Ordered(只影响"后处理"顺序)
对普通 Bean 的"实例化顺序 "没有任何影响,仅当 Spring 内部收集 BeanPostProcessor、FactoryPostProcessor 等扩展点时使用。