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