主类
@SpringBootApplication
@EnableImportApiVersion("cn.lacknb.blog.apiversiontest.service")
public class ApiVersionTestApplication {
public static void main(String[] args) {
SpringApplication.run(ApiVersionTestApplication.class, args);
}
}
跟进 run 方法中
SpringApplication
的构造方法中
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// primarySources 为 ApiVersionTestApplication.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据依赖的引用,判断当前容器是servlet,还是reactive,None
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载所有jar中的META-INF/spring.factories文件,找到对应的值列表
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 通过堆栈跟踪的方式推断出主类;因为启动的方式很多,主类不一定会通过参数传过来
this.mainApplicationClass = deduceMainApplicationClass();
}
org.springframework.boot.BootstrapRegistryInitializer
这个接口是 Spring Boot 框架用来在应用程序启动过程中进行早期配置的一个接口。其典型用法包括注册一些早期的单例对象或配置特定的环境属性。
org.springframework.context.ApplicationContextInitializer
Spring 应用上下文的初始化器,这些初始化器会在 ApplicationContext
刷新之前被调用,用于对上下文进行一些自定义的初始化操作
org.springframework.context.ApplicationListener
监听器,会在 Spring Boot 应用程序的生命周期中响应各种事件,例如应用程序的启动、上下文刷新、上下文关闭等。这个机制允许开发者在 Spring Boot 应用程序的不同阶段插入自定义逻辑,从而增强应用程序的灵活性和可扩展性。
Spring Boot在应用程序启动的不同阶段会触发一系列事件,例如:
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ApplicationReadyEvent
ApplicationFailedEvent
这些事件会被发布到
ApplicationListener
实例,并调用其onApplicationEvent
方法。
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
// 创建上下文,将上下文作为参数,调用所有的 BootstrapRegistryInitializer 的 initialize 初始化方法;默认是空的Initializer
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 读取 spring.factories 中的 SpringApplicationRunListener 的子类,放到 SpringApplicationRunListeners 中;
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用所有listener的starting方法 默认有一个listener:org.springframework.boot.context.event.EventPublishingRunListener
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
org.springframework.boot.context.event.EventPublishingRunListener
主要作用是通过在应用启动过程中的不同阶段发布事件,使得开发者可以通过事件监听器在这些关键点插入自定义逻辑,从而实现更灵活和可扩展的启动流程管理。这种机制不仅促进了启动流程和事件处理的解耦,还增强了应用的模块化和可测试性。
starting方法: 发布ApplicationStartingEvent事件
private final SimpleApplicationEventMulticaster initialMulticaster;
事件广播器
private Class<?> deduceMainApplicationClass() {
try {
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;
}
当你创建一个新的 RuntimeException
对象并调用其 getStackTrace
方法时,你可以获取当前线程的堆栈跟踪信息。堆栈跟踪信息是一个 StackTraceElement
对象的数组,表示方法调用的堆栈。
在堆栈跟踪数组中,按照调用顺序排列了方法调用的层级。程序的入口点,即 main
方法,总是会出现在堆栈跟踪信息的某个位置。通过遍历堆栈跟踪数组,可以找到调用 main
方法的类。
Spring Boot 应用程序启动的方式有很多,例如:
java -jar app.jar
SpringApplication.run(MyApplication.class, args);
在一些启动方式中,主类不一定会作为 primarySources
传递。例如,通过Spring Boot CLI启动时,主类可能是通过命令行参数传递的,而不是在代码中显式指定的。
deduceMainApplicationClass
方法提供了一种默认行为,即使在开发者没有显式指定主类的情况下,也能尽量推断出主类。这种方法增加了框架的智能性和易用性。
虽然在大多数情况下,primarySources
确实包含了主类,但为了增加Spring Boot的灵活性和通用性,deduceMainApplicationClass
方法提供了一种可靠的方式来推断主类。这种设计使得Spring Boot能够在多种不同的启动场景中都能正确工作,从而提高了框架的智能性和用户体验。
代码如下:
package cn.lacknb.blog.apiversiontest.config;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.time.Duration;
import java.util.Arrays;
public class MySpringApplicationRunning implements SpringApplicationRunListener {
public MySpringApplicationRunning(SpringApplication application, String[] args) {
System.out.print("MySpringApplicationRunning... ->");
System.out.print(application.getClass().getName());
System.out.println(Arrays.toString(args));
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("starting !");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("environmentPrepared !");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared !");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded !");
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("started !");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("ready !");
}
}
创建配置文件:resources/META-INF/spring.factories
内容如下:
org.springframework.boot.SpringApplicationRunListener=\
cn.lacknb.blog.apiversiontest.config.MySpringApplicationRunning
启动 Spring Boot 主类
控制台打印如下:
MySpringApplicationRunning... ->org.springframework.boot.SpringApplication[]
starting !
environmentPrepared !
contextPrepared !
contextLoaded !
started !
ready !
由此可以看出方法的执行顺序。具体在源码中是如何执行的,还需慢慢找。
starting 方法源码位置
SpringApplication
正在启动但未开始任何处理时调用。此时,还没有创建 ApplicationContext
,也没有进行任何配置加载。listeners.starting(bootstrapContext, this.mainApplicationClass);
// 更深层代码
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
遍历所有的 listener
执行 starting
方法
environmentPrepared
Environment
准备好后(即配置文件和环境变量已加载完成)调用,但在创建 ApplicationContext
之前。Environment
进行进一步配置或修改,例如添加新的属性源,调整系统属性等。contextPrepared
ApplicationContext
准备好之后调用,但在加载任何 bean 之前。ApplicationContext
进行进一步的自定义配置,例如注册一些特定的 bean 定义,添加或修改上下文的属性等。contextLoaded
ApplicationContext
已经加载并完成 bean 定义加载后调用,但在刷新上下文之前。started
ApplicationContext
刷新并启动后调用。但 CommandLineRunners
和 ApplicationRunners
尚未被调用。ready
在 run
方法结束前立即调用,表示应用程序已准备好接受请求。