SpringBoot启动流程
大致流程
关于SpringBoot的启动流程,大致是这样的
-
加载启动类
启动类是使用了@SpringBootApplication注解标注的类,该注解包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的功能。SpringBoot通过扫描启动类所在的包及子包,自动配置相应的Bean -
加载配置文件
SpringBoot程序默认从applicaiton.properties或application.yml中加载配置,也可以通过在启动类上标注@PropertySource来引入其他的配置文件 -
创建Spring容器
SpringBoot使用SpringBootApplication类创建Spring容器,SpringApplication类是SpringBoot的核心类,它提供了配置和管理Bean的方法。如果是Web应用,SpringApplication会创建一个内置的Web服务器 -
加载自动配置
SpringBoot通过@EnableAutoConfiguration来完成自动配置,根据starter依赖中的Configuration和Bean的装配情况,自动装配相应的Bean -
运行SpringBoot应用程序
当一切准备就绪后,SpringBoot就会启动应用程序,如果是Web应用,就会启动内置的Web服务器,如果使用的是Web服务器,可以将应用程序打包成一个可以直接运行的jar文件
源码解读
当我们执行启动类中的SpringApplication.run()
方法后,
SpringBoot的动作实际上可以大致分为两部分:实例化SpringApplication和运行SpringApplication
参考文章
9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂_Fly丶X的博客-CSDN博客
spring boot 启动流程分析 - 掘金
图片来自以上两篇文章
实例化SpringApplication
我们查看源码
当执行了静态方法SpringApplcation.run()
后,就来到了这两个方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
进入到SpringApplication的构造方法,在实例化中干了哪些事情?
public SpringApplication(Class<?>... primarySources) { this((ResourceLoader)null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = Collections.emptySet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.applicationContextFactory = ApplicationContextFactory.DEFAULT; this.applicationStartup = ApplicationStartup.DEFAULT; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); // 1. 判断此应用是否是Web应用 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 2.加载类加载器 this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 3. 加载监听器 this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 4. 设置主类 this.mainApplicationClass = this.deduceMainApplicationClass(); }
1. 判断应用类型
通过一个枚举类WebApplicationType的静态方法来判断应用类型
package org.springframework.boot; import org.springframework.util.ClassUtils; public enum WebApplicationType { NONE, SERVLET, REACTIVE; // Web 应用相关的类 private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"}; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer"; private WebApplicationType() { } static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) { return REACTIVE; } else { String[] var0 = SERVLET_INDICATOR_CLASSES; int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { String className = var0[var2]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return NONE; } } return SERVLET; } } }
在此方法中,通过扫描Classpath中是否有预设的几个Web应用相关的类,来判断此应用是否是一个Web应用。
此处提供了几个Web关键类的全限定名,拿到了全限定名,就可以利用反射的机制来判断是否存在这些类。
此枚举类有三个类型:
- NONE
- SERVLET
- REACTIVE
其中REACTIVE的意思就是标志这个SpringBoot应用程序就是一个普通的boot项目
2. 加载Initializer初始化构造器
此处是加载Spring-boot中自带的初始化器,并不是第三方的starter中的初始化器。
会扫描spring-boot自动的jar中META-INF/spring.factories
文件中定义的配置类和相关Bean
可以利用此机制,实现自己的初始化器。
3. 加载应用监听器Listener
同样,仍然是在spring-boot自带的jar中的META-INF/spring.factories
文件中加载。
此监听器的类型是ApplicationListener,也就是对整个应用程序的监听器。
4. 设置主类
我们的启动类,就是主类,使用了@SpringBootApplication注解标注,并且包含main方法的类。
通过deduceMainApplicationClass()方法来推断主类所在的位置,确定主类的位置,为后面的包扫描提供条件。
执行SpringApplication的run()
实例化完成SpringApplication后,就会执行SpringApplication实例的run()方法
在该方法中,大致完成了以下几件事情:
- 启动应用监听器
- 准备Environment
- 发布事件
- 创建上下文对象、bean
- 刷新refresh()上下文对象
- 结束
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
此方法会返回一个ConfigurableApplicationContext的实例,我们可以在启动类中拿到这个返回值。
看run()方法的源码
public ConfigurableApplicationContext run(String... args) { // 1. 开启计时 long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); // Spring上下文对象 ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); // 2. 实例化应用监听器并封装 SpringApplicationRunListeners listeners = this.getRunListeners(args); // 启动应用监听器 listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 3.准备环境参数以及初始化环境 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); // 4. 打印Banner,无所吊用 Banner printedBanner = this.printBanner(environment); // 5. 实例化Spring上下文对象 context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 6.Spring容器初始化 this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 7. 刷新容器 this.refreshContext(context); this.afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup); } listeners.started(context, timeTakenToStartup); this.callRunners(context, applicationArguments); } catch (Throwable var12) { this.handleRunFailure(context, var12, listeners); throw new IllegalStateException(var12); } try { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); return context; } catch (Throwable var11) { this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null); throw new IllegalStateException(var11); } }
1. 启动计时器
我现在使用的Springboot版本是2.7.10,发现取消了计时器这个类,而是直接通过来获取启动耗时
System.nanoTime()
如果你是低版本的Springboot,会发现有这样的几行代码,这就是启动计时器
// 实例化计时器 StopWatch stopWatch = new StopWatch(); // 开始计时 stopWatch.start();
不论是否使用了计时器,目的都是来记录SpringBoot的启动流程。
2.启动应用监听器
通过这几行代码
// 获取所有的应用监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args); // 启动 listeners.starting(bootstrapContext, this.mainApplicationClass);
在getRunListener()方法中
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class[]{SpringApplication.class, String[].class}; return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup); }
通过一个Class类型的数组来封装所有的监听器
再看SpringApplicationRunListeners的构造方法
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; private final ApplicationStartup applicationStartup; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners, ApplicationStartup applicationStartup) { this.log = log; this.listeners = new ArrayList(listeners); this.applicationStartup = applicationStartup; } }
在这个SpringApplicationRunListeners内部,通过一个List集合来封装所有的应用监听器,以此来达到统一管理所有的应用监听器
3.准备Environment
通过以下代码
// 准备应用参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 准备Environment ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Environment接口是对程序运行环境的抽象,是保存系统配置的中心
来打开prepareEnvironment()方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // 1. 创建Environment实例,自动根据环境的不同,创建对应的Environment实例 ConfigurableEnvironment environment = this.getOrCreateEnvironment(); // 2. 设置启动参数到Environment实例中 this.configureEnvironment(environment, applicationArguments.getSourceArgs()); // 3. 更新参数 ConfigurationPropertySources.attach(environment); // 4. 通过应用监听器来发布事件 listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); // 5. 绑定主类 this.bindToSpringApplication(environment); if (!this.isCustomEnvironment) { EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader()); environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
4. 实例化SpringContext对象
// 实例化SpringContext对象 context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 设置SpringContext参数 this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
在prepareContext()方法中,
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 1. 绑定环境Enviroment context.setEnvironment(environment); // 2. 如果Application有设置BeanName、resourceLoader等, // 就将其注入到Context中 this.postProcessApplicationContext(context); this.applyInitializers(context); // 3. 发布ApplicationContextInitializer事件 listeners.contextPrepared(context); bootstrapContext.close(context); if (this.logStartupInfo) { this.logStartupInfo(context.getParent() == null); this.logStartupProfileInfo(context); }
5. 刷新容器
对Context做出了一系列设置后,刷新容器
// 刷新SpringContext容器 this.refreshContext(context);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码