引言
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提供了指导和启示。