SpringBoot- 启动原理(下)

SpringBoot系列- 启动原理(下)

   从上一篇文章《SpringBoot系列-启动原理(上)中,介绍了SpringBoot应用启动的核心方法run()的整体情况。这篇文章来详细展开介绍。其中比较重要的方法会标记上***

   一、createBootstrapContext ***

   DefaultBootstrapContext bootstrapContext = createBootstrapContext();

   1. SpringApplication#createBootstrapContext方法:

1    private DefaultBootstrapContext createBootstrapContext() {
2      DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
3      this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
4      return bootstrapContext;
5    }

   DefaultBootstrapContext 是 Spring Boot 2.4 版本引入的,它是用来提供应用启动时的一些早期引导操作。它允许在创建 ApplicationContext 之前,提前加载和初始化某些关键组件或资源。这对于某些在启动时需要提前初始化的场景非常重要,例如:

  • 配置属性源的初始化(如 Environment 配置)
  • 提供一些应用启动时的依赖资源(如外部配置文件、网络服务的连接等)

   2. bootstrapContext 与 applicationContext 的区别

   1) bootstrapContext 

   bootstrapContext 是在 applicationContext 之前的一个轻量级上下文,它不会管理 bean 的生命周期,也不会处理依赖注入等常规操作。它主要用于在应用上下文完全初始化前,提前处理一些启动相关的任务。

   2) applicationContext  

   applicationContext 是完整的 Spring 容器,用于管理应用的 bean 生命周期、依赖注入、事件驱动等。

   二、getRunListeners ***

   SpringApplicationRunListeners listeners = this.getRunListeners(args);

   创建所有 Spring 运行监听器。从META-INF/spring.factories中获取并启动监听器。这些监听器都是SpringApplicationRunListener接口的实现类。 

   这里面涉及到SPI扩展机制,详细请参考文章《SpringBoot系列- SPI机制》

   1.  getRunListeners() 

   SpringApplication#getRunListeners() ,源码如下:  

1     private SpringApplicationRunListeners getRunListeners(String[] args) {
2         Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
3 
4         // 通过getSpringFactoriesInstances方法指定SpringApplicationRunListener 类型来加载的
5         // 注意下参数 this 就是当前的 SpringApplication 对象,用于获取监听器集合
6         return new SpringApplicationRunListeners(logger,
7                 getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
8                 this.applicationStartup);
9     }      

   2. getSpringFactoriesInstances() 

   SpringApplication#getSpringFactoriesInstances() ,源码如下:  

 1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
 2     return getSpringFactoriesInstances(type, new Class<?>[]{});
 3 }
 4 
 5 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
 6     ClassLoader classLoader = getClassLoader();
 7     // Use names and ensure unique to protect against duplicates
 8     //从META-INF/spring.factories中加载对应类型的类的自动配置类
 9     Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
10     // 加载上来后反射实例化
11     List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
12     //对实例列表进行排序
13     AnnotationAwareOrderComparator.sort(instances);
14     return instances;
15 }

    3. SpringFactoriesLoader

    SpringFactoriesLoader#loadFactoryNames

1 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
2         ClassLoader classLoaderToUse = classLoader;
3         if (classLoaderToUse == null) {
4             classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
5         }
6         String factoryTypeName = factoryType.getName();
7         return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
8     }

    SpringFactoriesLoader类的两个核心方法:loadFactoryNames() 是用来寻找spring.factories文件中的Factory名称的,loadFactories() 是用来寻找类的。

   三、starting ***

   listeners.starting(bootstrapContext, this.mainApplicationClass);

   广播SpringApplicationEvent事件,分发给监听器列表

   SpringApplicationRunListeners#starting(),源码如下:

1     void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
2         doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
3                 (step) -> {
4                     if (mainApplicationClass != null) {
5                         step.tag("mainApplicationClass", mainApplicationClass.getName());
6                     }
7                 });
8     }

   四、prepareEnvironment ***

   ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

   准备环境,项目运行环境Environment的预配置

   SpringApplication#prepareEnvironment(),源码如下:

 1     private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
 2             DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
 3         // 创建并配置当前SpringBoot应用将要使用的Environment
 4         ConfigurableEnvironment environment = getOrCreateEnvironment();
 5         configureEnvironment(environment, applicationArguments.getSourceArgs());
 6         ConfigurationPropertySources.attach(environment);
 7 
 8         //广播ApplicationEnvironmentPreparedEvent事件,分发给监听器列表
 9         listeners.environmentPrepared(bootstrapContext, environment);
10          //... 
11         return environment;
12     }

   五、printBanner 

   Banner printedBanner = printBanner(environment);

   创建 Banner 的打印类

   六、createApplicationContext ***

   context = this.createApplicationContext();

   创建Spring容器,即创建应用上下文。根据应用程序的类型,创建相应的ApplicationContext实例。

   1. SpringApplication#createApplicationContext()

1     protected ConfigurableApplicationContext createApplicationContext() {
3         return this.applicationContextFactory.create(this.webApplicationType);
5     }

   2. WebApplicationType 枚举类

1 public enum WebApplicationType {
2    NONE,
3    SERVLET,
4    REACTIVE;
5    //...
6 }

   WebApplicationType 是 Spring Boot 中的一个枚举类,用于表示应用程序的类型,即 Spring Boot 应用程序的运行环境类型。这个枚举类在 Spring Boot 启动时用于确定应用程序是 Web 应用程序(如 MVC、WebFlux), 还是非 Web 应用程序(如命令行工具)。它主要有以下三种类型:

   1)  NONE:  非 Web 应用(CLI、批处理等)。这种类型的应用程序不需要嵌入式的 Web 服务器,如 Tomcat、Jetty、Undertow 等。

   2) SERVLET: 基于 Servlet 的传统 Web 应用(如使用 Spring MVC)。此类型的应用程序需要嵌入式的 Web 服务器,并且支持 Spring MVC 来处理 HTTP 请求。

   3) REACTIVE: 基于反应式编程模型的 Web 应用(如使用 Spring WebFlux)。

   这里默认的应用程序类型为SERVLET

  七、prepareContext ***

  this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

  准备应用上下文:这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。

 1     private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
 2             ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
 3             ApplicationArguments applicationArguments, Banner printedBanner) {
 4 
 5         //广播ApplicationContextInitializedEvent事件,分发给监听器列表
 6         listeners.contextPrepared(context);
 7 
 8         //加载 Bean 定义,包括启动类
 9         load(context, sources.toArray(new Object[0]));
10 
11         //广播ApplicationPreparedEvent事件,分发给监听器列表
12         listeners.contextLoaded(context);
13 
14         //...
15     }

   上下文加载时,把通过SPI机制加载进来的监听器注册到上下文中。后面AbstractApplicationContext#refresh()方法中registerListeners()中会将上下文中的监听器取出来,注册到多播器上去。

   EventPublishingRunListener#contextLoaded方法

 

 1     @Override
 2     public void contextLoaded(ConfigurableApplicationContext context) {
 3         for (ApplicationListener<?> listener : this.application.getListeners()) {
 4             if (listener instanceof ApplicationContextAware) {
 5                 ((ApplicationContextAware) listener).setApplicationContext(context);
 6             }
 7             context.addApplicationListener(listener);
 8         }
 9         this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
10     }

   八、refreshContext ***

   this.refreshContext(context);

   这里调用了Spring的refresh方法,详细请参考文章《Spring容器系列-启动原理(下)》

1     private void refreshContext(ConfigurableApplicationContext context) {
2         if (this.registerShutdownHook) {
3             shutdownHook.registerApplicationContext(context);
4         }
5         refresh(context);
6     }

   这里我们看下refresh方法中第9个方法onRefresh()

   主要作用:模版方法,由子类重写。用于在容器刷新时执行特定的自定义操作:如创建Tomcat,Jetty等WEB服务器

   ServletWebServerApplicationContext#onRefresh()

 1     protected void onRefresh() {
 2         super.onRefresh();
 3         try {
 4             // 创建Tomcat,Jetty等WEB服务器
 5             createWebServer();
 6         }
 7         catch (Throwable ex) {
 8             throw new ApplicationContextException("Unable to start web server", ex);
 9         }
10     }

   可以看到在SpringBoot项目中,对onRefresh这个方法进行了重写,内嵌了Tomcat的启动。

   九、 afterRefresh

   afterRefresh(context, applicationArguments);

   应用上下文刷新之后的事件的处理

   SpringApplication#afterRefresh(),源码如下:

1 //扩展接口,设计模式中的模板方法,默认为空实现。
2 //如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
3 protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
4 }

   十、started ***

   listeners.started(context, timeTakenToStartup);

   广播ApplicationStartedEvent事件,分发给监听器列表

1     @Override
2     public void started(ConfigurableApplicationContext context, Duration timeTaken) {
3         context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
4         AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
5     }

   十一、callRunners

   callRunners(context, applicationArguments);

   调用并执行所有实现了 ApplicationRunner 和 CommandLineRunner 接口的类,使得在项目启动完成后立即执行一些特定程序。Runner 运行器用于在服务启动时进行一些业务初始化操作(比如初始化数据、加载资源、连接外部服务等),这些操作只在服务启动后执行一次。

   SpringApplication#callRunners(),源码如下:

 1     private void callRunners(ApplicationContext context, ApplicationArguments args) {
 2         List<Object> runners = new ArrayList<>();
 3         runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
 4         runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
 5         AnnotationAwareOrderComparator.sort(runners);
 6         for (Object runner : new LinkedHashSet<>(runners)) {
 7             if (runner instanceof ApplicationRunner) {
 8                 callRunner((ApplicationRunner) runner, args);
 9             }
10             if (runner instanceof CommandLineRunner) {
11                 callRunner((CommandLineRunner) runner, args);
12             }
13         }
14     }

   1. ApplicationRunner 接口

1 @FunctionalInterface
2 public interface ApplicationRunner {
3     void run(ApplicationArguments args) throws Exception;
4 }

   2. CommandLineRunner 接口

1 @FunctionalInterface
2 public interface CommandLineRunner {
3    void run(String... args) throws Exception;
4 }

  ApplicationRunner 和 CommandLineRunner 接口的主要区别在于命令行参数的处理方式:ApplicationRunner 接受已经解析的参数通过 ApplicationArguments 对象,而 CommandLineRunner 则是原始的字符串数组。

   十二、ready

   listeners.ready(context, timeTakenToReady); 

   广播ApplicationReadyEvent事件,分发给监听器列表

 

1     @Override
2     public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
3         context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
4         AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
5     }

 

posted @ 2024-09-06 11:31  欢乐豆123  阅读(3)  评论(0编辑  收藏  举报