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机制》
SpringApplicationRunListener在SpringBoot框架中只有一个实现类EventPublishingRunListener,它的主要职责是在应用启动的不同阶段触发事件并且广播给所有的监听器。
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 private final SpringApplication application; 2 3 4 @Override 5 public void contextLoaded(ConfigurableApplicationContext context) { 6 for (ApplicationListener<?> listener : this.application.getListeners()) { 7 if (listener instanceof ApplicationContextAware) { 8 ((ApplicationContextAware) listener).setApplicationContext(context); 9 } 10 context.addApplicationListener(listener); 11 } 12 this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context)); 13 }
说明:将监听器注册到 Spring 上下文的主要目的是使这些监听器能够响应和处理应用程序运行期间的重要事件。
SpringApplication类
1 public class SpringApplication { 2 3 private List<ApplicationListener<?>> listeners; 4 5 //构造器 6 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 7 //... 8 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); 9 } 10 11 public Set<ApplicationListener<?>> getListeners() { 12 return asUnmodifiableOrderedSet(this.listeners); 13 } 14 }
八、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); 执行所有的Runner运行器
调用并执行所有实现了 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 }