SpringBoot(springboot的类加载和传统的双亲委派有什么区别、如何按顺序实例化Bean) 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。目录结构示例如下mySpringBootApp.jar ├── BOOT-INF │ ├── classes用户代码 │ └── lib依赖的第三方jar └── org.springframework.boot.loader传统的Java类加载机制Application ClassLoader只能从外部classpath加载类无法直接加载JAR包内嵌的其他JARfat jar,因此SpringBoot加入了自定义的类加载器。主要做了哪些改造SpringBoot使用LaunchedURLClassLoader继承自URLClassLoader替代了ApplicationClassLoader通过运行时动态生成jar路径的URL来加载嵌套jar。LaunchedURLClassLoader会先加载BOOT-INF/classes目录下的应用类优先于JDK类。再加载BOOT-INF/lib/目录下的依赖JARLaunchedURLClassLoader会解析BOOT-INF/lib/下的每个jar将其URL添加到类路径中。最后再交给父类加载器即ApplicationClassLoader。总结一下为了加载嵌套在主JAR内部的fat jarSpringBoot在类加载流程上做了改造增加了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之前实例化指定的BeanBean 实例化/初始化顺序其实就是指“哪个 Bean 先被 new、哪个 PostConstruct 先跑”。目前有6种方式可以实现按照一定顺序进行实例化Bean。1、构造器依赖最稳无侵入Spring 保证一个 Bean 实例化之前它依赖的 Bean 必须已实例化。直接让 BeanA 的构造器里需要 BeanB或者 BeanA 里有一个非延迟的 BeanB 字段 Autowired。如下代码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 后销毁。如下代码Configuration public class Config { Bean public B b() { return new B(); } Bean DependsOn(b) // 容器会先实例化 b再实例化 a public A a() { return new A(); } }直接将注解写在类上也可以Component DependsOn(b) public class A { }3、Order 或 Ordered只影响“收集型”顺序适用范围Bean方法返回的是Collection注入点如ListX、MapString,X。CommandLineRunner / ApplicationRunner / Filter / Interceptor等“链式”扩展点。对普通的单例Bean实例化顺序无效。即使两个单例 Bean 实现了 Ordered 接口只要它们之间不存在“收集型注入”或“链式扩展点”Spring 仍然不保证谁先实例化。使用场景如下代码示例使用此注解的Bean都是实现了同一个接口的同类型。Component Order(1) public class FirstRunner implements CommandLineRunner { ... } Component Order(2) public class SecondRunner implements CommandLineRunner { ... }4、BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的扩展