引言
SpringBoot是当今Java开发中最受欢迎的微服务框架之一,其简化了Java应用的开发和部署过程。了解SpringBoot的启动流程对于深入理解其原理和内部机制至关重要。本文将深入分析SpringBoot的启动过程,探讨其中的关键步骤和机制,后基于这些机制,我们尝试做一些扩展和一些个性化内容。
启动流程分析
流程图
源代码
// org.springframework.boot.SpringApplication
public class SpringApplication {
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
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;
}
}
初始化 SpringApplication 实例
在SpringBoot应用启动时,首先会初始化一个SpringApplication实例。在这个过程中,会进行一系列配置和准备工作,包括:
指定资源加载器和主类
通过指定资源加载器和主类(即ApplicationStarter类),SpringBoot能够正确地加载应用所需的资源,并确定应用的入口点。
指定Web应用类型
SpringBoot支持多种Web应用类型,包括无Web环境、Servlet环境和响应式环境。通过指定不同的应用类型,可以适配不同的部署场景。
获取系统初始化器和监听器
SpringBoot允许用户注册自定义的初始化器和监听器,这些组件可以在应用启动过程中执行特定的逻辑,如配置环境、加载资源等。
执行 SpringApplication#run 方法
一旦SpringApplication实例初始化完成,接下来就会执行run方法来启动SpringBoot应用。在这个过程中,会经历以下关键步骤:
创建启动器上下文
首先,会创建一个启动器上下文,用于管理应用的生命周期和组件。
获取监听器集合
获取所有注册的SpringApplicationRunListener,并通知它们应用即将启动的事件。
处理环境相关事项
在启动过程中,会处理环境相关的配置,包括创建和配置ConfigurableEnvironment,并根据配置信息打印Banner。
创建和初始化应用上下文
这是整个启动过程的核心。首先,会创建一个ConfigurableApplicationContext,然后对其进行初始化。这个过程包括设置环境、注册单例Bean、初始化BeanFactory等。
启动应用上下文
一旦应用上下文初始化完成,就会启动应用上下文的生命周期,执行一系列初始化和准备工作。
通知监听器应用已启动
在应用启动完成后,会通知所有注册的监听器应用已经启动,可以执行相应的逻辑。
执行自定义逻辑
最后,会执行注册的ApplicationRunner和CommandLineRunner的run方法,这些方法中可以执行一些自定义的初始化逻辑。
应用扩展示例
在启动流程分析中,我们展示了路程图,其中流程图中蓝色部分就是我们能在 SpringBoot 应用启动过程中的所有扩展点(ApplicationContext IoC 容器生命周期中也还有,如 BeanPostProcessor 等,不过我们这里先不关注)。
下面我们就举出一个上面列出的扩展示例,其他如果有兴趣的朋友也可自行尝试,非常简单。
ApplicationContextInitializer
ApplicationContextInitializer 是 Spring Framework 提供的一个接口,它允许我们在 Spring 应用程序上下文(ApplicationContext)创建之前对其进行自定义初始化。这意味着我们可以在 Spring 容器初始化之前执行一些操作,例如设置环境变量、配置属性等。
下面我们展示一下如何使用:
- 定义一个 ApplicationContextInitializer 实现,如 MarkusApplicationContextInitializer。
public class MarkusApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("MarkusApplicationContextInitializer");
// 这里我们可以编写自定义的初始化逻辑
}
}
- 在 resources/ 目录下,创建一个 META-INF/spring.factories 文件,指定上我们的自定义实现。(这里就是 Spring 自己定义的 SPI 机制,框架可以自动发现自定义组件)
org.springframework.context.ApplicationContextInitializer=com.markus.spring.boot.extendsion.MarkusApplicationContextInitializer
- 启动 SpringBoot
@SpringBootApplication
public class SpringApplicationStarter {
/**
* Spring Boot 启动时的几个扩展点:
* 1. org.springframework.boot.SpringApplication#bootstrapRegistryInitializers
* 2. org.springframework.boot.SpringApplication#initializers
* 3. org.springframework.boot.SpringApplication#listeners
* 4. org.springframework.boot.SpringApplication#callRunners(org.springframework.context.ApplicationContext, org.springframework.boot.ApplicationArguments)
*
*/
public static void main(String[] args) {
SpringApplication.run(SpringApplicationStarter.class, args);
}
}
我们看下效果:
启动 Banner 修改
我们经常看到,在 SpringBoot 启动的时候,会打印如下图所示的 Banner:
那么,我们现在分析完了 SpringBoot 的启动过程后,就知道我们可以自定扩展这个文案,怎么扩展呢?
非常简单,在 application.yml 文件里指定一下自定义 banner 的文件位置,SpringBoot 就可以打印我们指定的文案了。
好了,现在我们在此启动 SpringBoot,图案就是我们自己设置的了:
本文总结
总结一下,本文深入分析了SpringBoot的启动流程,重点关注了SpringApplication的初始化和启动过程。在初始化SpringApplication实例时,会指定资源加载器、主类以及Web应用类型,并获取系统初始化器和监听器。随后,通过run方法启动SpringBoot应用,其中包括创建启动器上下文、获取监听器集合、处理环境相关事项、创建和初始化应用上下文、启动应用上下文、通知监听器应用已启动等关键步骤。文章还展示了如何通过ApplicationContextInitializer进行应用扩展,以及如何自定义Banner。通过本文,大家可以深入了解SpringBoot的启动过程及其扩展方式,为更深入地学习和应用SpringBoot提供了指导和启示。