赞
踩
关于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 启动流程分析 - 掘金
图片来自以上两篇文章
我们查看源码
当执行了静态方法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(); }
通过一个枚举类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关键类的全限定名,拿到了全限定名,就可以利用反射的机制来判断是否存在这些类。
此枚举类有三个类型:
此处是加载Spring-boot中自带的初始化器,并不是第三方的starter中的初始化器。
会扫描spring-boot自动的jar中META-INF/spring.factories
文件中定义的配置类和相关Bean
可以利用此机制,实现自己的初始化器。
同样,仍然是在spring-boot自带的jar中的META-INF/spring.factories
文件中加载。
此监听器的类型是ApplicationListener,也就是对整个应用程序的监听器。
我们的启动类,就是主类,使用了@SpringBootApplication注解标注,并且包含main方法的类。
通过deduceMainApplicationClass()方法来推断主类所在的位置,确定主类的位置,为后面的包扫描提供条件。
实例化完成SpringApplication后,就会执行SpringApplication实例的run()方法
在该方法中,大致完成了以下几件事情:
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); } }
我现在使用的Springboot版本是2.7.10,发现取消了计时器这个类,而是直接通过来获取启动耗时
System.nanoTime()
如果你是低版本的Springboot,会发现有这样的几行代码,这就是启动计时器
// 实例化计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
不论是否使用了计时器,目的都是来记录SpringBoot的启动流程。
通过这几行代码
// 获取所有的应用监听器
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集合来封装所有的应用监听器,以此来达到统一管理所有的应用监听器
通过以下代码
// 准备应用参数
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; }
// 实例化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);
}
对Context做出了一系列设置后,刷新容器
// 刷新SpringContext容器
this.refreshContext(context);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。