springboot启动流程

  • 一、简述
  • 二、注解
  • 三、启动
    • 1. 运行 SpringApplication.run() 方法
    • 2. 确定应用程序启动类型
    • 3. 加载所有的初始化器
    • 4. 加载所有的监听器
    • 5. 获取程序运行的主类
    • 6. 开启计时
    • 7.设置java.awt.headless为true
    • 8.获取并启用监听器
    • 9.设置应用程序参数
    • 10.准备环境变量
    • 11.打印banner信息
    • 12. 创建应用程序的上下文
    • 13.准备应用上下文
      • 13.1. 实例化单例的beanName生成器
      • 13.2. 执行初始化方法
      • 13.3. 将启动参数注册到容器中
    • 14. 刷新应用上下文
    • 15.刷新应用上下文后续处理(可扩展)
    • 16.结束计时
    • 17.发布上下文准备就绪事件
    • 18.执行自定义的run方法(可扩展)
    • 总结

一、简述

Spring Boot启动流程分析使用版本:

  • SpringBoot VERSION:版本 2.7.9;
  • JDK1.8

Spring Boot项目的Application启动类:

可以看出Application启动类中,包含了@SpringBootApplication 注解和 SpringApplication.run 启动方法,所以SpringBoot的启动可以分解为“注解”和 “启动方法”两大过程,其中启动方法中又可以分为两个阶段即创建SpringApplication实例和执行run方法。

二、注解


@SpringBootApplication注解内部可以发现,它虽然定义使用了多个Annotation,但实际上重要的只有三个Annotation:

  • @SpringBootConfiguration(@SpringBootConfiguration注解点开查看发现里面还是应用了@Configuration)->Spring IOC容器配置类;
  • @EnableAutoConfiguration ->使用@Import将所有符合自动配置条件的bean定义加载到IOC容器;
  • @ComponentScan ->自动扫描并加载符合条件的组件或者bean定义,默认扫描SpringApplication的run方法里的class所在的包路径下文件,所以通常将该启动类放到根包路径下。

三、启动

1. 运行 SpringApplication.run() 方法

一般来说springboot的应用程序都是从run方法开始的

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SpringBootDemo279Application {public static void main(String[] args) {//启动spring bootSpringApplication.run(SpringBootDemo279Application.class, args);}
}

进入run方法后,会 new 一个SpringApplication 对象,创建这个对象的构造函数做了一些准备工作,编号第2~5步就是构造函数里面所做的事情

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);}/*** Create a new {@link SpringApplication} instance. The application context will load* beans from the specified primary sources (see {@link SpringApplication class-level}* documentation for details). The instance can be customized before calling* {@link #run(String...)}.* @param resourceLoader the resource loader to use* @param primarySources the primary bean sources* @see #run(Class, String[])* @see #setSources(Set)*/@SuppressWarnings({ "unchecked", "rawtypes" })public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 初始化类加载器this.resourceLoader = resourceLoader;// Assert 断言非空,若传入的class参数为null则打印异常并退出初始化Assert.notNull(primarySources, "PrimarySources must not be null");// 获取main方法中的args,初始化启动时配置的额外参数集合this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 2.判断项目启动类型:NONE/SERVLET/REACTIVEthis.webApplicationType = WebApplicationType.deduceFromClasspath();// 3.加载初始化器this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//4.加载监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//5.推导出主应用程序类,即从当前的栈信息中寻找main所在主类this.mainApplicationClass = deduceMainApplicationClass();}

2. 确定应用程序启动类型

在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式项目);

...
// 2.判断项目启动类型:NONE/SERVLET/REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
....public enum WebApplicationType {/*** The application should not run as a web application and should not start an* embedded web server.*/NONE,/*** The application should run as a servlet-based web application and should start an* embedded servlet web server.*/SERVLET,/*** The application should run as a reactive web application and should start an* embedded reactive web server.*/REACTIVE;private static final String[] SERVLET_INDICATOR_CLASSES = { "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";/*** deduceFromClasspath* 依次循环遍历当前应用中是否存在相关的类来判断最终应用的启动类型** @return*/static WebApplicationType deduceFromClasspath() {/*** REACTIVE:响应式WEB项目* 若启动类型为REACTIVE,* 则类路径下存在 org.springframework.web.reactive.DispatcherHandler 类* 并且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer* 两者指的是SpringMVC/Tomcat和jersey容器*/if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}/*** NONE:非WEB项目,就是一个最简单的Springboot应用* 若启动类型为NONE* 则类路径下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在*/for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}/*** SERVLET:SERVLET WEB 项目* 若启动类型为Servlet,则必须有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet* 和org.springframework.web.context.ConfigurableWebApplicationContext*/return WebApplicationType.SERVLET;}}

3. 加载所有的初始化器

从 META-INF/spring.factories 配置文件加载springboot初始化器

...
// 3.加载初始化器
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
...private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicates/*** 加载各jar包中的"META-INF/spring.factories"配置* 其中SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法* 是获取spring.factories配置文件中已经配置的指定类型的的实现类集合* 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories*/Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 通过反射创建这些类List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);// 排序AnnotationAwareOrderComparator.sort(instances);return instances;}
.../*** Load the fully qualified class names of factory implementations of the* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given* class loader.* <p>As of Spring Framework 5.3, if a particular implementation class name* is discovered more than once for the given factory type, duplicates will* be ignored.* @param factoryType the interface or abstract class representing the factory* @param classLoader the ClassLoader to use for loading resources; can be* {@code null} to use the default* @throws IllegalArgumentException if an error occurs while loading factory names* @see #loadFactories*/public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());}/*** Springboot自动配置的秘密* Springboot在启动时读取了所有starter jar包里的META-INF/spring.factories配置文件,实现了所谓的自动化配置* 这里jar包里的都是默认配置,后续Springboot也会从xml、yaml文件中的用户配置去覆盖同名的配置。* 另外,这里的缓存配置是保存在一个map类型的cache中,其中的key键对应上面提到的各种Type类型,value就是Type的各种初始jar包里的同类                型Java类。*/private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {// 获取相应类加载器中内容Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {/*** 获取资源 -> META-INF/spring.factories 列表* 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories*/Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);// 可能存在多个META-INF/spring.factories 文件,循环加载while (urls.hasMoreElements()) {// 获取 META-INF/spring.factories 文件URL地址URL url = urls.nextElement();// 加载资源UrlResource resource = new UrlResource(url);// 加载资源配置Properties properties = PropertiesLoaderUtils.loadProperties(resource);// key:value形式循环配置for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();// 逗号分隔列表到字符串数组String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());// 循环value中子项到列表中for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elements// 列表去重result.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));// 列表保存cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;}

4. 加载所有的监听器

加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器加载的是实现了 ApplicationListener 接口的类

...
//4.加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
getSpringFactoriesInstances()同上,参数不同

5. 获取程序运行的主类

deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类;

...
//5.推导出主应用程序类,即从当前的栈信息中寻找main所在主类
this.mainApplicationClass = deduceMainApplicationClass();
.../*** 推导主应用程序类* @return*/
private Class<?> deduceMainApplicationClass() {try {// 获取当前的栈信息StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {// 获取main方法所在的类classif ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// Swallow and continue}return null;}

6. 开启计时

程序运行到这里,就已经进入了run方法的主体了,第一步调用的run方法是静态方法,那个时候还没实例化SpringApplication对象,现在调用的run方法是非静态的,是需要实例化后才可以调用的

/*** Run the Spring application, creating and refreshing a new* {@link ApplicationContext}.* @param args the application arguments (usually passed from a Java main method)* @return a running {@link ApplicationContext}*/public ConfigurableApplicationContext run(String... args) {//6.开始计时long startTime = System.nanoTime();//创建启动上下文对象即spring根容器DefaultBootstrapContext bootstrapContext = createBootstrapContext();// 定义可配置的应用程序上下文变量ConfigurableApplicationContext context = null;/*** 7.设置jdk系统属性* headless直译就是无头模式,* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;*/configureHeadlessProperty();/*** 8.获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法* 从spring.factories中获取配置*/SpringApplicationRunListeners listeners = getRunListeners(args);// 启动监听器listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 9.设置应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);/*** 10.准备环境 prepareEnvironment,里面主要涉及到* getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles* environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看*/ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置忽略的 beanconfigureIgnoreBeanInfo(environment);// 11.打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件Banner printedBanner = printBanner(environment);// 12.创建应用上下文(IOC 容器)context = createApplicationContext();// 设置一个启动器,设置应用程序启动context.setApplicationStartup(this.applicationStartup);// 13.准备应用程序上下文(配置 IOC 容器的基本信息 (spring容器前置处理))prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);/*** 14.刷新IOC容器* 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat*/refreshContext(context);/*** 15.留给用户自定义容器刷新完成后的处理逻辑* 刷新容器后的扩展接口(spring容器后置处理)*/afterRefresh(context, applicationArguments);// 16.结束计时器并打印,这就是我们启动后console的显示的时间Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {// 打印启动完毕的那行日志new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 17.发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法listeners.started(context, timeTakenToStartup);// 18.执行runner,遍历所有的 runner,调用 run 方法callRunners(context, applicationArguments);}catch (Throwable ex) {// 异常处理,如果run过程发生异常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;}

7.设置java.awt.headless为true

这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能

.../*** 7.设置jdk系统属性* headless直译就是无头模式,* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;*/
configureHeadlessProperty();
.../*** headless直译就是无头模式,* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
*/
private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

8.获取并启用监听器

这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情

  • 创建所有 Spring 运行监听器并发布应用启动事件
  • 启用监听器
/*** 8.获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法* 从spring.factories中获取配置*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);

9.设置应用程序参数

将执行run方法时传入的参数封装成一个对象

// 9.设置应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

10.准备环境变量

准备环境变量,包含系统属性和用户配置的属性等,里面主要涉及到getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfilesenvironmentPrepared、bindToSpringApplication、attach诸多方法

...
/**
* 10.准备环境 prepareEnvironment,里面主要涉及到
* getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
* environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
.../*** 准备环境** @param listeners* @param bootstrapContext* @param applicationArguments* @return*/
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments           applicationArguments) {// Create and configure the environment 创建和配置环境// 根据项目类型建环境ConfigurableEnvironmentConfigurableEnvironment environment = getOrCreateEnvironment();// 从环境中获取并设置 PropertySources 和 activeProfilesconfigureEnvironment(environment, applicationArguments.getSourceArgs());// 把 PropertySources 设置在自己PropertySources的第一个位置ConfigurationPropertySources.attach(environment);/*** 运行监听器调用* 广播事件,listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)* 发布事件通知所有的监听器当前环境准备完成*/listeners.environmentPrepared(bootstrapContext, environment);// 移动 defaultProperties 属性源到环境中的最后一个源DefaultPropertiesPropertySource.moveToEnd(environment);// 断言 抛异常Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");// 与容器绑定当前环境bindToSpringApplication(environment);// 若非web环境,将环境转换成StandardEnvironmentif (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 配置PropertySources对它自己的递归依赖ConfigurationPropertySources.attach(environment);return environment;}/*** 获取或创建环境Environment** @return*/private ConfigurableEnvironment getOrCreateEnvironment() {// 存在则直接返回if (this.environment != null) {return this.environment;}/*** 根据webApplicationType创建对应的Environment*/switch (this.webApplicationType) {// SERVLET WEB 项目case SERVLET:return new ApplicationServletEnvironment();// REACTIVE:响应式WEB项目case REACTIVE:return new ApplicationReactiveWebEnvironment();// 非WEB项目,就是一个最简单的Springboot应用default:return new ApplicationEnvironment();}}/*** 从环境中获取并设置 PropertySources 和 activeProfiles* 将配置任务按顺序委托给configurePropertySources和configureProfiles* Template method delegating to* {@link #configurePropertySources(ConfigurableEnvironment, String[])} and* {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.* Override this method for complete control over Environment customization, or one of* the above for fine-grained control over property sources or profiles, respectively.** @param environment this application's environment* @param args        arguments passed to the {@code run} method* @see #configureProfiles(ConfigurableEnvironment, String[])* @see #configurePropertySources(ConfigurableEnvironment, String[])*/protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {if (this.addConversionService) {environment.setConversionService(new ApplicationConversionService());}// 配置PropertySourcesconfigurePropertySources(environment, args);// 配置ProfilesconfigureProfiles(environment, args);}/*** 配置PropertySources* Add, remove or re-order any {@link PropertySource}s in this application's* environment.** @param environment this application's environment* @param args        arguments passed to the {@code run} method* @see #configureEnvironment(ConfigurableEnvironment, String[])*/protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {MutablePropertySources sources = environment.getPropertySources();// 初始化 defaultPropertiesif (!CollectionUtils.isEmpty(this.defaultProperties)) {// 存在的话将其放到最后位置DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);}/*** 存在命令行参数,则解析它并封装进SimpleCommandLinePropertySource对象* 同时将此对象放到sources的第一位置(优先级最高)*/if (this.addCommandLineProperties && args.length > 0) {String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;if (sources.contains(name)) {PropertySource<?> source = sources.get(name);CompositePropertySource composite = new CompositePropertySource(name);composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));composite.addPropertySource(source);sources.replace(name, composite);} else {// 放到首位sources.addFirst(new SimpleCommandLinePropertySource(args));}}}/*** 配置Profiles** @param environment* @param args*/protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {/*** 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性* 存在则将其值添加activeProfiles集合中。* 配置应用环境中的哪些配置文件处于激活状态(或默认激活)* 可以通过spring.profiles.active属性在配置文件处理期间激活其他配置文件* 就是我们项目中通常配置的dev、sit、prod等环境配置信息设置哪些Profiles是激活的。*/environment.getActiveProfiles(); // ensure they are initialized// But these ones should go first (last wins in a property key clash)// 如果存在其他的Profiles,则将这些Profiles放到第一的位置Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);profiles.addAll(Arrays.asList(environment.getActiveProfiles()));environment.setActiveProfiles(StringUtils.toStringArray(profiles));}/*** 运行监听器调用** @param bootstrapContext* @param environment*/void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {doWithListeners("spring.boot.application.environment-prepared",(listener) -> listener.environmentPrepared(bootstrapContext, environment));}/*** 运行监听器调用* Called once the environment has been prepared, but before the* {@link ApplicationContext} has been created.** @param environment the environment* @deprecated since 2.4.0 for removal in 2.6.0 in favor of* {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}*/@Deprecateddefault void environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {// 广播ApplicationEnvironmentPreparedEvent事件,后面再看listener.environmentPrepared(environment);}}/*** 与容器绑定当前环境* Bind the environment to the {@link SpringApplication}.** @param environment the environment to bind*/protected void bindToSpringApplication(ConfigurableEnvironment environment) {try {// 将environment绑定到SpringApplicationBinder.get(environment).bind("spring.main", Bindable.ofInstance(this));} catch (Exception ex) {throw new IllegalStateException("Cannot bind to SpringApplication", ex);}}/*** 配置PropertySources对它自己的递归依赖* Attach a {@link ConfigurationPropertySource} support to the specified* {@link Environment}. Adapts each {@link PropertySource} managed by the environment* to a {@link ConfigurationPropertySource} and allows classic* {@link PropertySourcesPropertyResolver} calls to resolve using* {@link ConfigurationPropertyName configuration property names}.* <p>* The attached resolver will dynamically track any additions or removals from the* underlying {@link Environment} property sources.** @param environment the source environment (must be an instance of*                    {@link ConfigurableEnvironment})* @see #get(Environment)*/public static void attach(Environment environment) {// 判断environment是否是ConfigurableEnvironment的实例Assert.isInstanceOf(ConfigurableEnvironment.class, environment);// 从environment获取PropertySourcesMutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);if (attached != null && attached.getSource() != sources) {sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);attached = null;}if (attached == null) {// 将sources封装成ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位置sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,new SpringConfigurationPropertySources(sources)));}}

11.打印banner信息

printBanner 打印SpringBoot标志。printBanner(environment)方法就是打印Banner,Banner就是项目启动时看到的那个logo。在工程项目src/main/resources路径下下放入名字是banner的文件,后缀后可以是SpringApplicationBannerPrinter.java类里的{ “gif”, “jpg”, “png” },或者是txt、图片也可以的,但是图片打印时会字符化,而不是打印图片本身。banner信息生成

...
// 11.打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
Banner printedBanner = printBanner(environment);
.../*** 打印SpringBoot标志* banner的输出默认有三种种模式,LOG、CONSOLE、OFF。* 1. LOG:将banner信息输出到日志文件。* 2. CONSOLE:将banner信息输出到控制台。* 3. OFF:禁用banner的信息输出。** @param environment* @return*/private Banner printBanner(ConfigurableEnvironment environment) {// 判断Banner的模式是否关闭,如果关闭直接返回。if (this.bannerMode == Banner.Mode.OFF) {return null;}ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);// 创建SpringApplicationBannerPrinter 打印类SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);// LOG:将banner信息输出到日志文件if (this.bannerMode == Mode.LOG) {return bannerPrinter.print(environment, this.mainApplicationClass, logger);}//banner没有关闭且没有指定是写到log文件中 将banner信息输出到控制台return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}/*** 打印** @param environment* @param sourceClass* @param logger* @return*/Banner print(Environment environment, Class<?> sourceClass, Log logger) {// 获取banner内容Banner banner = getBanner(environment);try {logger.info(createStringFromBanner(banner, environment, sourceClass));} catch (UnsupportedEncodingException ex) {logger.warn("Failed to create String for banner", ex);}return new PrintedBanner(banner, sourceClass);}/*** 获取banner内容** @param environment* @return*/private Banner getBanner(Environment environment) {Banners banners = new Banners();// 图片类型的banner内容banners.addIfNotNull(getImageBanner(environment));// 文本类型的banner内容banners.addIfNotNull(getTextBanner(environment));if (banners.hasAtLeastOneBanner()) {return banners;}if (this.fallbackBanner != null) {return this.fallbackBanner;}return DEFAULT_BANNER;}static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";static final String DEFAULT_BANNER_LOCATION = "banner.txt";/*** 文本类型的banner内容获取** @param environment* @return*/private Banner getTextBanner(Environment environment) {/*** 拿到自定义配置的banner文件地址* BANNER_LOCATION_PROPERTY = "spring.banner.location"* DEFAULT_BANNER_LOCATION = "banner.txt";*/String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);Resource resource = this.resourceLoader.getResource(location);try {if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {return new ResourceBanner(resource);}} catch (IOException ex) {// Ignore}return null;}

12. 创建应用程序的上下文

实例化应用程序的上下文, 调用 createApplicationContext() 方法

// 12.创建应用上下文(IOC 容器)
context = createApplicationContext();

13.准备应用上下文

13.1. 实例化单例的beanName生成器

在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称

13.2. 执行初始化方法

执行之前加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类

13.3. 将启动参数注册到容器中

这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments

...
// 13.准备应用程序上下文(配置 IOC 容器的基本信息 (spring容器前置处理))
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
.../*** 准备IOC容器基本信息* @param bootstrapContext* @param context* @param environment* @param listeners* @param applicationArguments* @param printedBanner*/private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {// 设置容器环境,包括各种变量context.setEnvironment(environment);/*** 后置处理流程* 设置IOC容器的 bean 生成器和资源加载器*/postProcessApplicationContext(context);/*** 获取所有的初始化器调用 initialize() 方法进行初始化* 执行容器中的ApplicationContextInitializer(包括从 spring.factories和自定义的实例)初始化*/applyInitializers(context);/*** 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法* 所有的运行监听器调用 environmentPrepared() 方法,EventPublishingRunListener 发布事件通知 IOC 容器准备完成*/listeners.contextPrepared(context);bootstrapContext.close(context);// 打印启动日志if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 注册添加特定的单例beanbeanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// Load the sources// 加载所有资源Set<Object> sources = getAllSources();// 断言资源费控Assert.notEmpty(sources, "Sources must not be empty");// 创建BeanDefinitionLoader,加载启动类,将启动类注入容器load(context, sources.toArray(new Object[0]));// 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法listeners.contextLoaded(context);}

14. 刷新应用上下文

refresh 刷新应用上下文,即刷新Spring上下文信息refreshContext。这里会涉及Spring容器启动、SpringBoot自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat。

...
/*** 14.刷新IOC容器* 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat*/
refreshContext(context);
.../*** 刷新应用上下文** @param context*/private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {// 判断是否注册关闭的钩子,是则注册钩子shutdownHook.registerApplicationContext(context);}refresh(context);}/*** Refresh the underlying {@link ApplicationContext}.** @param applicationContext the application context to refresh*/protected void refresh(ConfigurableApplicationContext applicationContext) {applicationContext.refresh();}/*** 刷新IOC容器** @throws BeansException* @throws IllegalStateException*/@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing. 准备刷新上下文prepareRefresh();// Tell the subclass to refresh the internal bean factory. 通知子类刷新内部工厂ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context. 准备Bean工厂prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 允许在上下文子类中对bean工厂进行后处理,这部分涉及Web服务器的启动,如servletpostProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.// 调用在上下文中注册为 bean 的工厂处理器invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation. 注册拦截 bean 创建的 bean 处理器registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context. 初始化此上下文的消息源initMessageSource();// Initialize event multicaster for this context. 为该上下文初始化事件多播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses. 初始化特定上下文子类中的其他特殊 bean/*** SpringBoot 一键启动web工程的关键方法* 创建 WebServer启动Web服务* SpringBoot启动内嵌的 Tomcat 首先要在pom文件配置内嵌容器为tomcat* SpringBoot 嵌入式 Servlet 容器,默认支持的 webServe:Tomcat、Jetty、Undertow*          <exclusion>*             <groupId>org.springframework.boot</groupId>*             <artifactId>spring-boot-starter-tomcat</artifactId>*         </exclusion>*/onRefresh();// Check for listener beans and register them. 检查侦听器 bean 并注册registerListeners();// Instantiate all remaining (non-lazy-init) singletons. 实例化所有剩余的(非延迟初始化)单例finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event. 发布事件finishRefresh();} catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.  销毁beandestroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;} finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}}

15.刷新应用上下文后续处理(可扩展)

afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,

...
/*** 15.留给用户自定义容器刷新完成后的处理逻辑* 刷新容器后的扩展接口(spring容器后置处理)*/
afterRefresh(context, applicationArguments);
.../*** Called after the context has been refreshed.* @param context the application context* @param args the application arguments*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

16.结束计时

到这一步,springboot其实就已经完成了,打印启动springboot的时长

// 16.结束计时器并打印,这就是我们启动后console的显示的时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {// 打印启动完毕的那行日志new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}

17.发布上下文准备就绪事件

started 发布监听应用启动事件。

...
// 17.发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
listeners.started(context, timeTakenToStartup);
.../*** 发布应用监听启动事件* @param context*/void started(ConfigurableApplicationContext context) {// listener.started(context) 中交由context.publishEvent()方法处理// 实际上是发送了一个ApplicationStartedEvent的事件doWithListeners("spring.boot.application.started", (listener) -> listener.started(context));}

18.执行自定义的run方法(可扩展)

callRunners,执行runner主要是遍历所有的runner获取所有的ApplicationRuner 和CommandLineRunner 来初始化参数,其中callRuner(是一个回调函数)。
这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:

  • 实现 ApplicationRunner 接口
  • 实现 CommandLineRunner 接口
...
// 18.执行runner,遍历所有的 runner,调用 run 方法
callRunners(context, applicationArguments);
.../*** 执行runner 初始化参数* @param context* @param args*/private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);// 遍历所有runnerfor (Object runner : new LinkedHashSet<>(runners)) {if (runner instanceof ApplicationRunner) {/*** 回调函数callRunner 处理 ApplicationRunner*/callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {/*** 回调函数callRunner 处理 CommandLineRunner*/callRunner((CommandLineRunner) runner, args);}}}

总结

SpringBoot启动流程总结就是下面两张图片,一个创建SpringApplication实例,一个执行run方法。

  • 创建SpringApplication实例
  • 执行run方法

参考:
9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂
Spring Boot启动流程

Java springboot启动流程相关推荐

  1. Java面试--SpringBoot启动流程

    一.SpringBoot是什么 SpringBoot 是依赖于 Spring 的,比起 Spring,除了拥有 Spring 的全部功能以外,SpringBoot 无需繁琐的 Xml 配置,这取决于它 ...

  2. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  3. SPRINGBOOT启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

  4. SpringBoot入门到精通-SpringBoot启动流程(七)

    定义自己的starter SpringBoot入门到精通-Spring的注解编程(一) SpringBoot入门到精通-SpringBoot入门(二) SpringBoot入门到精通-Spring的基 ...

  5. [springboot]springboot启动流程

    Spring Boot程序有一个入口,就是main方法.main里面调用SpringApplication.run()启动整个Spring Boot程序,该方法所在类需要使用@SpringBootAp ...

  6. SpringBoot 启动流程(细节拉满)

    SpringBoot 启动流程(细节拉满) 网上搜了一下,这方面网上的资料不少,有些从@SpringBootApplication来入手讲.个人不能苟同吧,讲一讲我的理解.还有一些讲的比较笼统,我来写 ...

  7. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  8. Springboot启动流程分析(四):完成启动流程

    目录 一 添加BeanPostProcessors到IOC容器 二 国际化支持 三 初始化监听器的多路播放器 四 刷新容器 五 注册监听器到IOC容器的多播器 六 完成bean的大规模实例化 6.1 ...

  9. SpringBoot启动流程简要

    SpringBoot启动流程大概: 初始化SpringApplication 根据项目的配置情况和Conditional条件来推断是否是一个Web应用. 读取所有jar包下面spring.factor ...

最新文章

  1. hello python jpush api_jpush python服务器端
  2. java中的session对象,Java对象中Response与session对象的方法是什么?
  3. 四十五、爬取QQ音乐Lemon 日语歌的评论
  4. 电大计算机网络技术基础,电大--2016年电大 计算机与网络技术基础小抄已排版.doc...
  5. mysql唯一性约束冲突_如何解决逻辑删除与数据库唯一约束冲突
  6. 四:Cocos2d-x设计思想
  7. Wrise - 与众不同的文字编辑器
  8. 3.8086/8088微处理器结构
  9. 【Zigbee】基础篇(4) Zigbee无线通信过程、无线发送温湿度信息
  10. 学会使用这些常见的网络诊断工具,助力你的网络编程之路
  11. 《淘宝网开店 拍摄 修图 设计 装修 实战150招》一一
  12. VMware虚拟机关闭U盘USB自动识别
  13. html和css最全的知识点归纳总结,html和css的面试知识点总结(附示例)
  14. 成都拓嘉启远:拼多多开店怎样节约物流成本
  15. 为什么4G信号满格,但是就是打不开网页
  16. 红外线计件器课程设计报告书
  17. Swift Programming Tutorial for Beginners(Full Tutorial)
  18. 2021-04-28 Mac上插入公式的三种方法
  19. 为 Nexus 5 准备的 Sailfish 移植版本
  20. 如何通过平台注册到过期域名?

热门文章

  1. 百度编辑器过滤div标签问题
  2. 统一配置Outlook组策略实现统一管理
  3. 如何借助spire.doc,在 Word 中插入 OLE 对象
  4. Unity Shader - 在 URP 获取 Ambient(环境光) 颜色
  5. Safari浏览器删除不了数据怎么办
  6. vue自定义指令:实现弹窗全屏和窗口化切换,拖动弹窗
  7. python怎么用拼音-Python利用拼音库PyPinyin获取汉字的拼音
  8. android电话面试技巧,看懂这些帮你轻松解决就业问题
  9. Android 点击拍照,长按录像保存本地 结合camer2实现 前后摄像头切换,手机闪关灯,
  10. Introduce C