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

发布于:2025-07-18 ⋅ 阅读:(20) ⋅ 点赞:(0)

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

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

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

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

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

	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;
	}
public enum WebApplicationType {
    /**
     * 不需要嵌入式 web 容器,不是 web 应用
     */
    NONE,

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

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

补充:

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

网站公告

今日签到

点亮在社区的每一天
去签到