目录
Spring Boot源码学习
/        

Spring Boot源码学习

运行 Debug

SpringApplication 构造方法

主类

@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方法。

Run 方法

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; 事件广播器

扩展知识

1、堆栈跟踪

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方法的类。

2、为什么要通过堆栈跟踪来确定主类

Spring Boot 应用程序启动的方式有很多,例如:

  • java -jar app.jar
  • 直接调用 SpringApplication.run(MyApplication.class, args);
  • 使用Spring Boot插件,例如Maven或Gradle插件
  • 通过Spring Boot CLI
  • 甚至可以通过单元测试启动

在一些启动方式中,主类不一定会作为 primarySources传递。例如,通过Spring Boot CLI启动时,主类可能是通过命令行参数传递的,而不是在代码中显式指定的。

deduceMainApplicationClass方法提供了一种默认行为,即使在开发者没有显式指定主类的情况下,也能尽量推断出主类。这种方法增加了框架的智能性和易用性。

虽然在大多数情况下,primarySources确实包含了主类,但为了增加Spring Boot的灵活性和通用性,deduceMainApplicationClass方法提供了一种可靠的方式来推断主类。这种设计使得Spring Boot能够在多种不同的启动场景中都能正确工作,从而提高了框架的智能性和用户体验。

3、SpringApplicationRunner样例

代码如下:

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 定义加载后调用,但在刷新上下文之前。
    • 用途:可以在这里执行一些在上下文刷新之前需要完成的操作,例如检查和修改 bean 定义,准备其他上下文资源等。
  • started

    • 作用:该方法在 ApplicationContext 刷新并启动后调用。但 CommandLineRunnersApplicationRunners尚未被调用。
    • 用途:可以在这里执行一些在应用完全启动后需要完成的操作,例如启动额外的线程,初始化后台任务等。
  • ready

    run方法结束前立即调用,表示应用程序已准备好接受请求。


标题:Spring Boot源码学习
作者:gitsilence
地址:http://blog.lacknb.cn/articles/2024/06/02/1717292837180.html