一、简述

Spring的启动过程就是IoC容器的启动过程,本质上就是创建和初始化Bean的工厂(BeanFactory),BeanFactory是整个SpringIoC的核心,Spring使用BeanFactory来实例化、配置和管理Bean。

二、SpringBoot的启动过程

在SpringBoot中,SpringApplication封装了一套Spring应用的启动流程,对用户完全是透明的,这个类原本在Spring中是没有的。

一般来说,默认的SpringApplication执行流程可以满足大部分需求,若是想要干预这过程,可以通过SpringApplication在流程的某些地方开启扩展点来对流程进行扩展,典型的扩展方案就是setXXX方法.

1 @SpringBootApplication
2 public class CodeSheepApplication {
3 public static void main( String[] args ) {
4 // SpringApplication.run( DdsApplication.class, args );
5 SpringApplication app = new SpringApplication( DdsApplication.class );
6 app.setXXX( ... ); // 用户自定的扩展在此 !!!
7 app.run( args );
8 }
9 }

SpringBoot应用中,首先要了解的就是SpringApplication这个类了。

SpringApplication的实例化,在上面这段代码中,使用了自定义SpringApplication,通过一行代码启动SpringBoot应用,也可以自定义SpringApplication的一些扩展。

 1   @SuppressWarnings({ "unchecked", "rawtypes" })
2 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
3 this.resourceLoader = resourceLoader;
4 Assert.notNull(primarySources, "PrimarySources must not be null");
5 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
6 this.webApplicationType = WebApplicationType.deduceFromClasspath();
7 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
8 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
9 this.mainApplicationClass = deduceMainApplicationClass();
10 }

1.WebApplicationType是一个枚举Web Application的类型,其类型定义有三种:NONE(不应作为Web应用程序运行,也不应启动嵌入式Web服务器)、SERVLET(基于servlet的Web应用程序)、REACTIVE(响应式Web应用程序)。

NONE:org.springframework.context.annotation.AnnotationConfigApplicationContext

SERVLET:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

REACTIVE:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext

WebApplicationType#deduceFromClasspath()的意思是根据Classpath的内容推断WebApplication类型。

 1   static WebApplicationType deduceFromClasspath() {
2 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
3 && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
4 return WebApplicationType.REACTIVE;
5 }
6 for (String className : SERVLET_INDICATOR_CLASSES) {
7 if (!ClassUtils.isPresent(className, null)) {
8 return WebApplicationType.NONE;
9 }
10 }
11 return WebApplicationType.SERVLET;
12 }

ClassUtils是Spring框架所提供关于类级别的工具类,主要供框架内部使用。ClassUtils#isPresent是判断当前class loader中是否存在对应的类型。代码里面关于判断中的方法,为什么所提供的classLoader参数都为空,再进去方法里面看看,发现它是调用了ClassUtils#forName

 1 public static boolean isPresent(String className, @Nullable ClassLoader classLoader)    {
2 try {
3 forName(className, classLoader);
4 return true;
5 }
6 catch (IllegalAccessError err) {
7 throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
8 className + "]: " + err.getMessage(), err);
9 }
10 catch (Throwable ex) {
11 // Typically ClassNotFoundException or NoClassDefFoundError...
12 return false;
13 }
14 }

再看看ClassUtils#forName,方法的源码很长,它替换Class.forName(),还返回原始类型(例如“int”)和数组类名称(例如“String []”)的Class实例。 此外,它还能够以Java源代码样式解析内部类名(例如“java.lang.Thread.State”而不是“java.lang.Thread $ State”)。

里面调用了ClassUtils#resolvePrimitiveClassName来把给定的类作为基本类(如果适用的话);如果不是基本类型则在缓存找,如果是基本类型则返回;然后接着判断给出的类名是不是一维或多维的整型或字符串数组;接着判断方法传入的classLoader,为空则ClassUtils#getDefaultClassLoader来从当前System中获取默认的classLoader,再使用给定的类加载器返回与具有给定字符串名称的类或接口关联的Class对象→Class.forName(类名, false, 当前默认的classLoader),当找不到Class就会报异常。

 1 public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
2 throws ClassNotFoundException, LinkageError {
3 ​
4 Assert.notNull(name, "Name must not be null");
5 ​
6 Class<?> clazz = resolvePrimitiveClassName(name);
7 if (clazz == null) {
8 clazz = commonClassCache.get(name);
9 }
10 if (clazz != null) {
11 return clazz;
12 }
13 ​
14 // "java.lang.String[]" style arrays
15 if (name.endsWith(ARRAY_SUFFIX)) {
16 String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
17 Class<?> elementClass = forName(elementClassName, classLoader);
18 return Array.newInstance(elementClass, 0).getClass();
19 }
20 ​
21 // "[Ljava.lang.String;" style arrays
22 if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
23 String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
24 Class<?> elementClass = forName(elementName, classLoader);
25 return Array.newInstance(elementClass, 0).getClass();
26 }
27 ​
28 // "[[I" or "[[Ljava.lang.String;" style arrays
29 if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
30 String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
31 Class<?> elementClass = forName(elementName, classLoader);
32 return Array.newInstance(elementClass, 0).getClass();
33 }
34 ​
35 ClassLoader clToUse = classLoader;
36 if (clToUse == null) {
37 clToUse = getDefaultClassLoader();
38 }
39 try {
40 return Class.forName(name, false, clToUse);
41 }
42 catch (ClassNotFoundException ex) {
43 int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
44 if (lastDotIndex != -1) {
45 String innerClassName =
46 name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
47 try {
48 return Class.forName(innerClassName, false, clToUse);
49 }
50 catch (ClassNotFoundException ex2) {
51 // Swallow - let original exception get through
52 }
53 }
54 throw ex;
55 }
56 }

总结WebApplicationType#deduceFromClasspath(),它通过ClassPath来推断WebApplication的类型,从当前系统默认的ClassLoader获取WebApplication类型相对应类的映射,从而判断WebApplication的类型。

1 setInitializers((Collection)
2 getSpringFactoriesInstances(ApplicationContextInitializer.class));

2.setInitializers(...)使用 SpringFactoriesLoader 查找并加载 classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer ---- 用于在ConfigurableApplicationContext#refresh() 之前初始化Spring ConfigurableApplicationContext的回调接口。

 1 # PropertySource Loaders
2 org.springframework.boot.env.PropertySourceLoader=\
3 org.springframework.boot.env.PropertiesPropertySourceLoader,\
4 org.springframework.boot.env.YamlPropertySourceLoader
5 ​
6 # Run Listeners
7 org.springframework.boot.SpringApplicationRunListener=\
8 org.springframework.boot.context.event.EventPublishingRunListener
9 ​
10 # Error Reporters
11 org.springframework.boot.SpringBootExceptionReporter=\
12 org.springframework.boot.diagnostics.FailureAnalyzers
13 ​
14 # Application Context Initializers
15 org.springframework.context.ApplicationContextInitializer=\
16 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
17 org.springframework.boot.context.ContextIdApplicationContextInitializer,\
18 org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
19 org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
20 ​
21 # Application Listeners
22 org.springframework.context.ApplicationListener=\
23 org.springframework.boot.ClearCachesApplicationListener,\
24 org.springframework.boot.builder.ParentContextCloserApplicationListener,\
25 org.springframework.boot.context.FileEncodingApplicationListener,\
26 org.springframework.boot.context.config.AnsiOutputApplicationListener,\
27 org.springframework.boot.context.config.ConfigFileApplicationListener,\
28 org.springframework.boot.context.config.DelegatingApplicationListener,\
29 org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
30 org.springframework.boot.context.logging.LoggingApplicationListener,\
31 org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
32 ​
33 # Environment Post Processors
34 org.springframework.boot.env.EnvironmentPostProcessor=\
35 org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
36 org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
37 org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
38 ​
39 # Failure Analyzers
40 org.springframework.boot.diagnostics.FailureAnalyzer=\
41 org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
42 org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
43 org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
44 org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
45 org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
46 org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
47 org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
48 org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
49 org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
50 org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
51 org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
52 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
53 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
54 ​
55 # FailureAnalysisReporters
56 org.springframework.boot.diagnostics.FailureAnalysisReporter=\
57 org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

3.setListeners(...) 使用SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener ---- 应用程序事件侦听器实现的接口, 基于Observer设计模式的标准java.util.EventListener 接口。

4.deduceMainApplicationClass()推断并设置main方法的定义类,通过Throwable#getStackTrace()方法返回堆栈中的元素,找出方法名为main的堆栈元素,再根据类名返回对应 的反射类

 1 private Class<?> deduceMainApplicationClass() {
2 try {
3 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
4 for (StackTraceElement stackTraceElement : stackTrace) {
5 if ("main".equals(stackTraceElement.getMethodName())) {
6 return Class.forName(stackTraceElement.getClassName());
7 }
8 }
9 }
10 catch (ClassNotFoundException ex) {
11 // Swallow and continue
12 }
13 return null;
14 }

再来看看SpringBoot应用运行方法 SpringApplication#run

 1 public ConfigurableApplicationContext run(String... args) {
2 StopWatch stopWatch = new StopWatch();
3 stopWatch.start();
4 ConfigurableApplicationContext context = null;
5 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
6 configureHeadlessProperty();
7 SpringApplicationRunListeners listeners = getRunListeners(args);
8 listeners.starting();
9 try {
10 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
11 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
12 configureIgnoreBeanInfo(environment);
13 Banner printedBanner = printBanner(environment);
14 context = createApplicationContext();
15 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
16 new Class[] { ConfigurableApplicationContext.class }, context);
17 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
18 refreshContext(context);
19 afterRefresh(context, applicationArguments);
20 stopWatch.stop();
21 if (this.logStartupInfo) {
22 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
23 }
24 listeners.started(context);
25 callRunners(context, applicationArguments);
26 }
27 catch (Throwable ex) {
28 handleRunFailure(context, ex, exceptionReporters, listeners);
29 throw new IllegalStateException(ex);
30 }
31 ​
32 try {
33 listeners.running(context);
34 }
35 catch (Throwable ex) {
36 handleRunFailure(context, ex, exceptionReporters, null);
37 throw new IllegalStateException(ex);
38 }
39 return context;
40 }

三、总结

获取SpringApplicationListener → 通知Listeners start → 创建参数,配置Environment → 根据WebApplicationType创建ApplicationContext → 初始化ApplicationContext,设置Environment加载相关配置等 → 通知EnvironmentPrepared,contextLoaded → refresh ApplicationContext → 通知Listeners start context → 完成启动→ 通知runner → 结束

  1. 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象

  2. 然后由 SpringApplicationRunListener 来发出 starting 消息

  3. 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment

  4. 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息

  5. 根据 WebApplicationType 创建 ApplicationContext

  6. 初始化 ApplicationContext,并设置 Environment,加载相关配置等

  7. SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK

  8. 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK

  9. refresh ApplicationContext,完成IoC容器可用的最后一步

  10. SpringApplicationRunListener 来发出 started 消息

  11. 完成最终的程序的启动

  12. SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了

SpringBoot:Spring容器的启动过程的更多相关文章

  1. spring容器的启动过程

    spring的启动过程: 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环 ...

  2. Spring 容器的启动过程 流程图 自己看源码的梳理 如有错错误 请指正

  3. Spring源码系列——容器的启动过程(一)

    一. 前言 Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气.俗话说,打蛇打七寸,那么Spring家族的"七寸"是什么呢?我心目中的答案一 ...

  4. Servlet容器的启动过程

    [http://book.51cto.com/art/201408/448854.htm]   Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会继承Lifecycle接口,它管理着容器的整 ...

  5. Spring MVC的启动过程

    一.概述 下面一个基本的运用springMVC的的web.xml的配置,这里要注意两个地方,一个是ContextLoadListener,一个是DispatcherServlet.web容器正是通过这 ...

  6. docker学习(3) 容器的启动过程

    这一节我们来稍微了解下docker原理性的东西 docker run -i -t ubuntu /bin/bash 输入上面这行命令,启动一个ubuntu容器时,到底发生了什么? 大致过程可以用下图描 ...

  7. spring注解配置启动过程

    最近看起spring源码,突然想知道没有web.xml的配置,spring是怎么通过一个继承于AbstractAnnotationConfigDispatcherServletInitializer的 ...

  8. 转:spring的启动过程-spring和springMVC父子容器的原理

    要想很好理解这三个上下文的关系,需要先熟悉spring是怎样在web容器中启动起来的.spring的启动过程其实就是其IoC容器的启动过程,对于web程序,IoC容器启动过程即是建立上下文的过程. s ...

  9. spring的启动过程就是创建ioc容器的过程

    1. spring简介 spring的最基本的功能就是创建对象及管理这些对象之间的依赖关系,实现低耦合.高内聚.还提供像通用日志记录.性能统计.安全控制.异常处理等面向切面的能力,还能帮我们管理最头疼 ...

随机推荐

  1. 【第二十篇】-Maven IntelliJ之Spring Cloud直播商城 b2b2c电子商务技术总结

    Maven IntelliJ IntelliJ IDEA 已经内建了对 Maven 的支持.我们在此例中使用的是 IntelliJ IDEA 社区版 11.1. IntelliJ IDEA 的一些特性 ...

  2. CLR无法从COM 上下文*****转换为COM上下文*****,这种状态已持续60秒。

    异常信息:CLR无法从COM 上下文0x645e18 转换为COM上下文0x645f88,这种状态已持续60秒.拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的 ...

  3. idea创建Maven项目没有src目录,且依赖也没有更新

    刚开始用idea的时候,重新配置安装了Maven,但是创建项目的时候发现创建的目录少了很多东西,今天重新查看了一下,发现了原因....... 话不多说,安装配置Maven的方法网上都有,我之前改路径的 ...

  4. 深入浅出 BPF TCP 拥塞算法实现原理

    本文地址:https://www.ebpf.top/post/ebpf_struct_ops 1. 前言 eBPF 的飞轮仍然在快速转动,自从 Linux 内核 5.6 版本支持 eBPF 程序修改 ...

  5. composer install 出现 RuntimeException Failed to execute

    报错:composer.json 的  require添加新包 需要删除composer.lock和vender 从新composer install [RuntimeException] Faile ...

  6. python numpy loadtxt

    用numpy加载csv文件数据 发现python numpy loadtxt 方法和数据的结构有很大关系当我的数据有第一行文字是这样子的时候 我程序的运行结果永远都报错,编码格式也处理了统一utf-8 ...

  7. 初探webpack之编写plugin

    初探webpack之编写plugin webpack通过plugin机制让其使用更加灵活,以适应各种应用场景,当然也大大增加了webpack的复杂性,在webpack运行的生命周期中会广播出许多事件, ...

  8. C++ Primer 查漏补缺 —— C++ 中的各种初始化

    初学者在刚开始读 C++ Primer 的时候,总是容易被书中各种初始化搞得头大:默认初始化.列表初始化.值初始化.类内初始值.构造函数初始值列表.new int 和 new int() 的区别... ...

  9. JVM 面试题,安排上了!!!

    肝了一篇非常硬核的 JVM 基础总结,写作不易,小伙伴们赶紧点赞.转发安排起来! 原文链接 据说看完这篇 JVM 要一小时 JVM 的主要作用是什么? JVM 就是 Java Virtual Mach ...

  10. 解决vue项目中遇到父组件的按钮或操作控制重新挂载子组件但是子组件却无效果的情况

    在vue项目中终会遇到需要父组件的按钮或操作控制重新挂载子组件的需求,我在新项目中就遇到这种需求.真实场景是父组件的早,中,晚三个按钮(代表三个时间段)来控制子组件的table表格列的动态加载. 子组 ...