springboot并未破坏双亲委派,而是通过springfactoriesloader结合线程上下文类加载器(tccl)实现受控的逆向委派:框架类由高层加载器加载,却主动委托appclassloader加载用户扩展类,本质是遵循jvm spi契约的标准实践。

SpringBoot 并没有直接“破坏”双亲委派,而是通过 SpringFactoriesLoader 配合线程上下文类加载器(TCCL)实现了一种受控的逆向委派——它不改写 loadClass(),也不绕过父加载器,而是让高层加载器(如 Bootstrap 或 Extension)在需要时,主动委托当前应用的类加载器去加载用户定义的扩展类。这才是真正看穿它的关键。
SpringFactoriesLoader 本身不破坏委派,它只是个读取工具
SpringFactoriesLoader 的核心逻辑非常简单:调用 ClassLoader.getResources("META-INF/spring.factories"),遍历所有匹配的资源,解析键值对,再用同一个 ClassLoader 去加载 value 中的类名。它不做任何类加载决策,也不切换加载器——它用谁的 ClassLoader,取决于你传入的 ClassLoader 参数,或默认使用当前线程的 TCCL。
常见调用方式:
-
SpringFactoriesLoader.loadFactoryNames(..., classLoader)—— 显式传入加载器 -
SpringFactoriesLoader.loadSpringFactories(ClassLoader)—— 默认用Thread.currentThread().getContextClassLoader()
也就是说,破坏与否,不在 SpringFactoriesLoader 代码里,而在谁调用它、用哪个 ClassLoader 调用它。
真正的“骚操作”藏在自动配置启动阶段
SpringBoot 启动时,SpringApplication.run() 会触发 getSpringFactoriesInstances(ApplicationContextInitializer.class) 等一系列加载。此时线程上下文类加载器(TCCL)已被设为 AppClassLoader(即加载你 application.jar 的那个加载器)。于是:
- 哪怕
SpringFactoriesLoader类本身由 Bootstrap 或 Ext 加载器加载(比如来自spring-boot的 jar 在 classpath),它读取到META-INF/spring.factories后,仍会用 TCCL(也就是你的 AppClassLoader)去加载其中声明的org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration这类用户级自动配置类 - 这就实现了“高层代码(框架)调用底层加载器(应用)来加载扩展类”,是典型的 逆向委派,也是 JVM 官方认可的 SPI 模式
对比 JDBC 的 DriverManager,逻辑完全一致
和 java.sql.DriverManager 一样:
-
DriverManager类由 Bootstrap 加载(在rt.jar中) - 但它内部调用
ServiceLoader.load(Driver.class)时,默认使用 TCCL - 因此能加载
mysql-connector-java.jar中的com.mysql.cj.jdbc.Driver(由 AppClassLoader 加载) - SpringFactoriesLoader 就是 Spring 版的
ServiceLoader,只是配置文件从META-INF/services/xxx换成了META-INF/spring.factories,机制一模一样
为什么这不算“暴力破坏”,而是一种设计契约
它没覆盖 loadClass(),没屏蔽父委派,也没让 Bootstrap 去直接读你项目里的 class 文件。它依赖的是:
- JVM 允许线程持有自己的 ClassLoader(TCCL 是公开 API)
- 框架层约定:SPI 扩展必须由当前应用环境的类加载器负责加载
- 所有主流容器(Tomcat、Spring Boot、Dubbo)都遵守这个契约,而非各自 hack
所以这不是漏洞利用,而是标准玩法——你看穿的不是“破坏”,而是框架如何借力 TCCL 实现可插拔。











