当前位置: 首页 > article >正文

SpringBoot源码(四):run() 方法解析(一)

run()方法:

public ConfigurableApplicationContext run(String... args) {
    // 记录应用启动时间
    long startTime = System.nanoTime();
    
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    // 创建 ConfigurableApplicationContext 对象
    ConfigurableApplicationContext context = null;
    // 设置系统的 awt (保证在没有检测到显示器的情况下(如linux服务器),SpringBoot应用也可以启动)
    configureHeadlessProperty();
    // 【】创建 SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 调用 SpringApplicationRunListener 的 starting 方法,发布了一个 ApplicationStartingEvent 事件 【】
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 【】准备运行时环境 Environment
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        // 
        configureIgnoreBeanInfo(environment);
        // 创建Banner对象、打印 Banner(会在控制台中打印 SPRING 图案,和 SpringBoot 的版本号)
        Banner printedBanner = printBanner(environment);
        // 【】创建IOC容器
        context = createApplicationContext();
        // 启动度量
        context.setApplicationStartup(this.applicationStartup);
        // 【】初始化IOC容器
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 【】刷新IOC容器(会同时在控制台打印Tomcat有关的信息)
        refreshContext(context);
        // 空实现
        afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            // 在控制台打印,消耗时间的信息:Started MyApplication in 209.698 seconds (JVM running for 215.496)
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        // 调用 SpringApplicationRunListener 的 started 方法,发布了一个 ApplicationStartedEvent 事件 【】
        listeners.started(context, timeTakenToStartup);
        // 回调所有的运行器
        callRunners(context, applicationArguments);
    }//..
    
    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        // 调用 SpringApplicationRunListener 的 ready 方法,发布了一个 ApplicationReadyEvent 事件 【】
        listeners.ready(context, timeTakenToReady);
    }//..
    
    // 返回IOC容器(所以 SpringApplication.run() 方法是有返回值的)
    return context;
}


获取 SpringApplicationRunListeners

在 SpringApplication 的 run() 方法内,有这样一步:

SpringApplicationRunListeners = getRunListeners(args)

获取 SpringApplicationRunListeners ,SpringApplicationRunListeners 其实就是装SpringApplicationRunListener 的容器

SpringApplicationRunListener

SpringApplicationRunListener 看名字就可以知道它是一个监听器,只不过它只负责监听 run() 方法,由于 run() 方法过于复杂,且整个 run() 方法中涉及很多切入点和扩展点,留有一个监听器可以在预定义好的切入点中扩展自定义逻辑。

SpringApplicationRunListener 提供了一系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。下面我们对照源代码和注释来了解一下该接口都定义了哪些待实现的方法及功能:

public interface SpringApplicationRunListener {
	// 当 run() 调用时,会被立即调用,可用于非常早期的初始化工作
    default void starting(ConfigurableBootstrapContext bootstrapContext) {}
    // 当 environment 对象准备完成,在 IOC 容器创建之前,该方法被调用
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {}
	// 当 IOC 创建完成,资源还未被加载时,该方法被调用
    default void contextPrepared(ConfigurableApplicationContext context) {}
	// 当 IOC 资源加载完成,未被刷新时,该方法被调用
    default void contextLoaded(ConfigurableApplicationContext context) {}
	// 当 IOC 刷新完并启动之后,未调用 CommandLineRunner 和 ApplicationRunner 时,该方法被调用
    default void started(ConfigurableApplicationContext context, Duration timeTaken) {
 started(context); }
    // 所有准备工作就绪,调用该方法
    default void ready(ConfigurableApplicationContext context, Duration timeTaken) { running(context); }
	// 当应用程序出现错误时,调用该方法
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }   
    //...
}

可以看出,SpringApplicationRunListener 为 run 方法提供了各个运行阶段的监听事件处理功能:

listeners

继续分析 getRunListeners 方法:

SpringApplication # getRunListeners()

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };   
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}

SpringApplicationRunListeners 的构造方法传入了三个参数:这里关注第二个参数

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args) (这里把 this(当前SpringApplication 对象)传入了方法

会通过 SpringFactoriesLoader 加载并实例化外部定义的所有 SpringApplicationRunListener (默认只有一个:EventPublishingRunListener )
并且这些 SpringApplicationRunListener 必须有一个接收 (SpringApplication application,String[] args)参数类型的构造器

EventPublishingRunListener 是 SpringBoot 中针对 SpringApplicationRunListener 接口的唯一内建实现

注意看注释:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {  // 注意这里的 Object... args 参数包含了 SpringApplication 对象
    ClassLoader classLoader = getClassLoader();
    // 获取类路径下 META-INF/spring.factories 中所有的 SpringApplicationRunListener 的名字(默认只有一个:EventPublishingRunListener)
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 实例化这个 EventPublishingRunListener(会利用 args 构建 EventPublishingRunListener对象)
    // 【注意】如果之后在 META-INF/spring.factories 中又扩展了许多 SpringApplicationRunListener,那么同样的,会利用 args 构建这些 SpringApplicationRunListener 对象
    // 即,这些实例化好的这些 SpringApplicationRunListener 一定会持有 SpringApplication 对象,从而拥有它里面的一些属性(如,可以获取所有的监听器对象)
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    // 返回
    return instances;
}

EventPublishingRunListener 的构造方法如下:

【注】创建好的 EventPublishingRunListener 对象会拥有 SpringApplication 对象里的 ApplicationListener 等属性

public EventPublishingRunListener(SpringApplication application, String[] args) {
    // 持有了 SpringApplication 对象
    this.application = application;
    this.args = args;
    // 初始化了一个事件多播器 SimpleApplicationEventMulticaster
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    // 将 SpringApplication 对象里的 ApplicationListener 全部加到了多播器中
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener); 
    }
}

通过源代码可以看出,该类的构造方法符合 SpringApplicationRunListener 所需的构造方法参数要求,该方法依次传递了 SpringApplication 和 String[] 类型。

在构造方法中初始化了该类的3个成员变量:

​ ① application:类型为 SpringApplication,是当前运行的 SpringApplication 实例

​ ② args:启动程序时的命令参数

​ ③ initialMulticaster:类型为 SimpleApplicationEventMulticaster,事件广播器

SpringBoot 完成基本的初始化之后,会遍历 SpringApplication 的所有 ApplicationListener 实例,并将它们与 SimpleApplicationEventMulticaster 进行关联,方便后续将事件传递给所有的监听器。

EventPublishingRunListener 针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同:

​ ● 程序启动到某个步骤后,调用 EventPublishingRunListener 的某个方法

​ ● EventPublishingRunListener 的具体方法将 application 参数和 args 参数封装到对应的事件中(这里的事件均为 SpringApplicationEvent 的实现类)

​ ● 通过成员变量 initialMulticaster 的multicastEvent方法对事件进行广播,或通过该方法的 ConfigurableApplicationContext context 参数的 publishEvent() 方法来对事件进行发布

​ ● 对应的 ApplicationListener 被触发,执行相应的业务逻辑

如:EventPublishingRunListener 的 starting() 方法:

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}


listeners.starting()

之后的一步: 调用 SpringApplicationRunListeners 的 starting 方法:【注】此时 SpringApplicationRunListeners 已经拥有了之前创建好的 EventPublishingRunListener 对象。

SpringApplicationRunListeners # starting()

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),  
                    (step) -> {
                        if (mainApplicationClass != null) {
                            step.tag("mainApplicationClass", mainApplicationClass.getName());
                        }
                    });
}

SpringApplicationRunListeners # doWithListeners()

private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) {
    StartupStep step = this.applicationStartup.start(stepName);
    // 遍历 this.listeners(目前只有一个EventPublishingRunListener),执行它们的 starting(bootstrapContext) 方法
    this.listeners.forEach(listenerAction); 
    if (stepAction != null) {
        stepAction.accept(step);  
    }
   
    step.end();
}

EventPublishingRunListener # starting()

可以看到方法内部,利用事件多播器,发布了一个 ApplicationStartingEvent 事件

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}

以下三个监听器会监听到,并调用它们的 onApplicationEvent 方法:

① LoggingApplicationListener、
② BackgroundPreinitializer、
③ DelegatingApplicationListener

它们都注册在 META-INF/spring.factories 中

总结

【注】SpringApplicationRunListeners,它相当于一系列 SpringApplicationRunListener 的组合 这些 SpringApplicationRunListener 的实现类都在 META-INF/spring.factories 中注册

(目前只有一个:EventPublishingRunListener)

private final List<SpringApplicationRunListener> listeners

如:调用 SpringApplicationRunListeners 的 starting(),则会遍历 List< SpringApplicationRunListener> listeners 里所有的 SpringApplicationRunListener ,依次调用它们的 starting()

其实就只有一个: EventPublishingRunListener,调用它的 starting(),而方法内部就会通过广播器将对应的事件广播给EventPublishingRunListener 所持有的 ApplicationListener 对象


http://www.kler.cn/a/378684.html

相关文章:

  • 使用 MONAI Deploy 在 AMD GPU 上进行全身分割
  • AI打造超写实虚拟人物:是科技奇迹还是伦理挑战?
  • aws(学习笔记第十课) 对AWS的EBS如何备份(snapshot)以及使用snapshot恢复数据,AWS实例存储
  • 你丢失的数据,10款数据恢复软件帮你找!!
  • 【Rust标准库中的convert(AsRef,From,Into,TryFrom,TryInto)】
  • QT——记事本项目
  • 微服务架构面试内容整理-微服务与传统单体架构的区别
  • 在麒麟V10上下载pycharm
  • Pinctrl子系统中client端设备树相关数据结构介绍和解析
  • 【双目视觉标定】——1原理与实践
  • XSS跨站脚本攻击的实现原理及讲解
  • 第三百零八节 Log4j教程 - Log4j日志到数据库
  • 江协科技STM32学习- P35 硬件I2C读写MPU6050
  • NFTScan Site:以蓝标认证与高级项目管理功能赋能 NFT 项目
  • lua学习笔记---面向对象
  • NVR批量管理软件/平台EasyNVR多个NVR同时管理支持对接阿里云、腾讯云、天翼云、亚马逊S3云存储
  • spark-本地模式的配置和简单使用
  • 【Unity】鼠标点击获取世界坐标位置:物体移动至鼠标点击的位置
  • 设计模式讲解01-建造者模式(Builder)
  • ZDS 数字股票 布局全球视野,开启智能金融新篇章
  • 秒杀优化(异步秒杀,基于redis-stream实现消息队列)
  • node.js rc4加密/解密 不好用怎么办?
  • 中文分词模拟器
  • 双十一晚会停办,一个消费时代结束了
  • 鸿蒙网络编程系列43-仓颉版HttpRequest下载文件示例
  • 第02章 MySQL环境搭建