一、简述

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. 【LeetCode】862. 和至少为 K 的最短子数组

    862. 和至少为 K 的最短子数组 知识点:单调:队列:前缀和 题目描述 返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K . 如果没有和至少为 K 的非空子数组,返回 -1 . 示例 ...

  2. ARM架构安装ubuntu系统

    一.简介 arm开发板制作系统是比较麻烦,不论使用busybox还是yocto制作根文件系统对新手都比太友好,除非深度定制,否则使用ubuntu系统既可以满足,把更多的精力放在应用开发上. 二.准备材 ...

  3. CLion远程调试嵌入式开发板程序

    CLion远程调试嵌入式开发板程序 目录 CLion远程调试嵌入式开发板程序 1. 目的 2. 前提条件 3. CLion设置 3.1 设置一个Deployment 3.2 上传需要的目录到目标板子 ...

  4. 使用Postman做接口测试(学生信息的6个接口)

    使用postman做接口测试,案例中涉及到接口有:获取学生信息.登录.添加学生信息.学生金币充值.获取所有学生信息.文件上传. 一.获取学生信息(get请求) 请求方式选择:get 直接在访问地址栏中 ...

  5. django url配置-反向解析-视图函数-HttpRequest对象-HttpResponse对象-cookies-session-redis缓存session

    """ --视图概述:-- 作用:视图接受WEB请求,并响应WEB请求 本质:视图就是一个python中的函数 响应: 1.网页: 一.重定向 二.错误视图 400,50 ...

  6. Python项目生成requirements.txt文件之pipreqs的使用

    生成requirements.txt时使用pip freeze > requirements.txt会将环境下所有的安装包都进行生成,再进行安装的时候会全部安装很多没有的包.耗时耗力其实是不可取 ...

  7. CF960G-Bandit Blues【第一类斯特林数,分治,NTT】

    正题 题目链接:https://www.luogu.com.cn/problem/CF960G 题目大意 求有多少个长度为\(n\)的排列,使得有\(A\)个前缀最大值和\(B\)个后缀最大值. \( ...

  8. Python3入门系列之-----return返回值,我终于懂了

    前言 初学者学习return的用法有点蒙,不知道它的作用是什么?返回的是什么?在什么时候要用?小伙伴也可能会遇到和我同样的困扰,给大家举个例子,马上就明白了. 同一段代码,函数中带return和没有r ...

  9. Matlab 速记

    链接:https://zhuanlan.zhihu.com/p/370259237 % 1.进度提醒 f = waitbar(0,'1','Name','进度'); set(f,'color','w' ...

  10. 域名系统-DNS

    域名系统DNS 域名系统DNS(Domain Name System)是互联网使用的命名系统,用来把便于人们使用的机器名转化为IP地址,域名系统就是名字系统. 很多应用层的软件经常直接使用DNS.DN ...