为什么不直接使用 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));// 推断应用类型,一般是webthis.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 会发布 ApplicationStartingEventlisteners.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发布 ApplicationReadyEventlisteners.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