SpringBoot为什么使用new RuntimeException() 来获取调用栈?

为什么不直接使用 Thread.currentThread().getStackTrace()?

这确实看起来有点"奇怪"或者"绕",但其实这是 Java 中一种非常常见、巧妙且合法的技巧,用于在运行时动态获取当前代码的调用栈信息。

Spring 选择用 new RuntimeException().getStackTrace() 是有原因的,主要有以下几点区别:

特性 new Exception().getStackTrace() Thread.currentThread().getStackTrace()
调用栈更详细 ✅ 包括每个类和方法名 ❌ 可能只包含类名,不包含具体方法名
性能开销 略高(需要构造异常对象) 较低
精确性 ✅ 更精确地定位到调用者 ❌ 在某些 JVM 实现中可能不准确
使用场景 需要精确调用栈时(如框架内部) 快速查看线程堆栈(调试、日志等)

所以 Spring 框架为了确保能够准确找到调用链中的 main 方法所在类,选择了第一种方式。

java 复制代码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 注册启动类
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 推断应用类型,一般是web
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 注册初始化器 (扩展接口)
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 注册监听器  (扩展接口)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 找到启动的主方法入口
        this.mainApplicationClass = deduceMainApplicationClass();
	}

	private Class<?> deduceMainApplicationClass() {
		try {
            // 使用RuntimeException().getStackTrace()获取调用栈
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}
java 复制代码
public enum WebApplicationType {
    /**
     * 不需要嵌入式 web 容器,不是 web 应用
     */
    NONE,

    /**
     * 需要嵌入式的 web 容器(如 Tomcat, Jetty)
     */
    SERVLET,

    /**
     * 使用 reactive web stack(如 Netty + WebFlux)
     */
    REACTIVE;
}

补充:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 监听器触发1: EventPublishingRunListener 会发布 ApplicationStartingEvent
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备应用所需的环境信息
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
            // 打印Banner的logo和日志
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
            // 准备应用上下文,包括设置环境、注册初始 Bean 定义、触发上下文准备完成事件
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新应用上下文 (核心)
			refreshContext(context);
            // 应用上下文刷新后的一些操作
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            // 监听器触发2:ApplicationReadyListener发布 ApplicationReadyEvent
			listeners.started(context);
            // 调用所有 CommandLineRunner 和 ApplicationRunner 接口实现类的 run 方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
            // 监听器触发3:对于 running(context) 并没有直接对应的事件被发布,执行一些运行时的监控或通知逻辑,比如通知外部系统应用已就绪
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
  • starting() - 触发 ApplicationStartingEvent
  • environmentPrepared() - 触发 ApplicationEnvironmentPreparedEvent
  • contextPrepared() - 触发 ApplicationContextInitializedEvent
  • contextLoaded() - 触发 ApplicationPreparedEvent
  • started(context) - 触发 ApplicationReadyEvent
相关推荐
非优秀程序员3 小时前
第07章 孙德良的阳谋|《互联网产品恩仇录》
javascript·后端
非优秀程序员3 小时前
第06章 浙江帮的内战|《互联网产品恩仇录》
后端
非优秀程序员3 小时前
第08章 马云的反击|《互联网产品恩仇录》
后端
非优秀程序员3 小时前
题记|《互联网产品恩仇录》
后端·程序员·创业
非优秀程序员3 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻3 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
文丑颜不良啊5 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang6 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
Gopher_HBo7 小时前
Go CPU 剖析的"地基":信号、CAS、写屏障、无锁环形缓冲
后端
panbook7 小时前
从0到1实现一个分布式时序数据库(二):写入成功之后,如何用 WAL 找回数据
后端