为什么不直接使用 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