SpringBoot的启动流程源码解析

简介: 在拥有 Spring Boot 以前,我们要运行一个 Java Web 应用,首先需要有一个 Web 容器(例如 Tomcat ),然后将我们的 Web 应用打包后放到容器的相应目录下,最后再启动容器。


1 前言

在拥有 Spring Boot 以前,我们要运行一个 Java Web 应用,首先需要有一个 Web 容器(例如 Tomcat ),然后将我们的 Web 应用打包后放到容器的相应目录下,最后再启动容器。


在 IDE 中也需要对 Web 容器进行一些配置,才能够运行或者 Debug。而使用 Spring Boot 我们只需要像运行普通 JavaSE 程序一样,run 一下 main () 方法就可以启动一个 Web 应用了。


2 追本溯源

只需要下面几行代码我们就可以跑起一个 Web 服务器:


@SpringBootApplicationpublicclassSpringbootApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
    }
}


去掉类的声明和方法定义这些样板代码,核心代码就只有一个 @SpringBootApplication 注解和 SpringApplication.run (SpringbootApplication.class, args) 方法。而我们知道注解相当于是一种配置,那么这个 run () 方法必然就是 Spring Boot 的启动入口了。


3 容器启动流程

接下来,我们沿着 run () 方法来顺藤摸瓜。进入 SpringApplication 类,来看看 run () 方法的具体实现:


publicclassSpringApplication {
    ......
publicConfigurableApplicationContextrun(String... args) {
// 1 应用启动计时开始StopWatchstopWatch=newStopWatch();
stopWatch.start();
// 2 声明上下文ConfigurableApplicationContextcontext=null;
// 3 初始化异常报告集合Collection<SpringBootExceptionReporter>exceptionReporters=newArrayList<>();
// 4 设置 java.awt.headless 属性configureHeadlessProperty();
// 5 启动监听器SpringApplicationRunListenerslisteners=getRunListeners(args);
listeners.starting();
try {
// 6 初始化默认应用参数ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);
// 7 准备应用环境ConfigurableEnvironmentenvironment=prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 8 打印 Banner(Spring Boot 的 LOGO)BannerprintedBanner=printBanner(environment);
// 9 通过反射创建上下文实例context=createApplicationContext();
// 10 构建异常报告exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,
newClass[] { ConfigurableApplicationContext.class }, context);
// 11 构建上下文prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 12 刷新上下文refreshContext(context);
// 13 刷新上下文后处理afterRefresh(context, applicationArguments);
// 14 应用启动计时结束stopWatch.stop();
if (this.logStartupInfo) {
// 15 打印启动时间日志newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    }
// 16 发布上下文启动完成事件listeners.started(context);
// 17 调用 runnerscallRunners(context, applicationArguments);
  }
catch (Throwableex) {
// 18 应用启动发生异常后的处理handleRunFailure(context, ex, exceptionReporters, listeners);
thrownewIllegalStateException(ex);
  }
try {
// 19 发布上下文就绪事件listeners.running(context);
  }
catch (Throwableex) {
handleRunFailure(context, ex, exceptionReporters, null);
thrownewIllegalStateException(ex);
  }
returncontext;
    }
    ......
}



Spring Boot 启动时做的所有操作都这这个方法里面,当然在调用上面这个 run () 方法之前,还创建了一个 SpringApplication 的实例对象。因为上面这个 run () 方法并不是一个静态方法,所以需要一个对象实例才能被调用。

可以看到,方法的返回值类型为 ConfigurableApplicationContext,这是一个接口,我们真正得到的是 AnnotationConfigServletWebServerApplicationContext 的实例。通过类名我们可以知道,这是一个基于注解的 Servlet Web 应用上下文(上下文(context)是 Spring 中的核心概念)。


3.1 应用启动计时

在 Spring Boot 应用启动完成时,我们经常会看到类似下面内容的一条日志:

Started SpringbootApplication in 4.9 seconds (JVM running for 5.553)


应用启动后,会将本次启动所花费的时间打印出来,让我们对于启动的速度有一个大致的了解,也方便我们对其进行优化。记录启动时间的工作是 run () 方法做的第一件事,由 stopWatch.start() 开启时间统计,具体代码如下:


publicvoidstart(StringtaskName) throwsIllegalStateException {
if (this.currentTaskName!=null) {
thrownewIllegalStateException("Can't start StopWatch: it's already running");
    }
// 记录启动时间this.currentTaskName=taskName;
this.startTimeNanos=System.nanoTime();
}


然后到了 run () 方法的基本任务完成的时候,由 stopWatch.stop ()(编号 14 的位置)对启动时间做了一个计算,源码也很简单:



publicvoidstop() throwsIllegalStateException {
if (this.currentTaskName==null) {
thrownewIllegalStateException("Can't stop StopWatch: it's not running");
    }
// 计算启动时间longlastTime=System.nanoTime() -this.startTimeNanos;
this.totalTimeNanos+=lastTime;
    ......
}



最后,在 run () 中的编号 15 的位置将启动时间打印出来:


if (this.logStartupInfo) {
// 打印启动时间newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}


3.2 打印 Banner

Spring Boot 每次启动是还会打印一个自己的 LOGO,就像下面这样:


这种做法很常见,像 Redis、Docker 等都会在启动的时候将自己的 LOGO 打印出来。Spring Boot 默认情况下会打印那个标志性的 “树叶” 和 “Spring” 的字样,下面带着当前的版本。

在 run() 中编号 8 的位置调用打印 Banner 的逻辑,最终由 SpringBootBanner 类的 printBanner() 完成。这个图案定义在一个常量数组中,代码如下:



classSpringBootBannerimplementsBanner {
privatestaticfinalString[] BANNER= {
"", 
"  .   ____          _            __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", 
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", 
"  '  |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/"    };
    ......
publicvoidprintBanner(Environmentenvironment, Class<?>sourceClass, PrintStreamprintStream) {
for (Stringline : BANNER) {
printStream.println(line);
  }
  ......
    }
}



真正打印的逻辑就是 printBanner () 方法里面的那个 for 循环。



3.3 创建上下文实例createApplicationContext

下面我们来到 run () 方法中编号 9 的位置,这里调用了一个 createApplicationContext() 方法,点进去我们会看到它的代码如下:


publicstaticfinalStringDEFAULT_SERVLET_WEB_CONTEXT_CLASS="org.springframework.boot."+"web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
protectedConfigurableApplicationContextcreateApplicationContext() {
Class<?>contextClass=this.applicationContextClass;
if (contextClass==null) {
try {
switch (this.webApplicationType) {
caseSERVLET:
contextClass=Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
caseREACTIVE:
contextClass=Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass=Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
catch (ClassNotFoundExceptionex) {
thrownewIllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}


这个方法就是根据 SpringBootApplication 的 webApplicationType 属性的值,利用反射来创建不同类型的应用上下文(context)。而属性 webApplicationType 的值是在前面执行构造方法的时候由 `WebApplicationType.deduceFromClasspath()`获得的。通过方法名很容易看出来,就是根据 classpath 中的类来推断当前的应用类型。



我们这里是一个普通的 Web 应用,所以最终返回的类型为 SERVLET。所以会通过反射加载 DEFAULT_SERVLET_WEB_CONTEXT_CLASS,最后返回一个 `AnnotationConfigServletWebServerApplicationContext`实例(就像我们上文所说的那样)。


3.4 构建容器上下文prepareContext

接着我们来到 run () 方法编号 11 的 prepareContext () 方法。通过方法名,我们也能猜到它是为 context 做上台前的准备工作的。


privatevoidprepareContext(ConfigurableApplicationContextcontext, ConfigurableEnvironmentenvironment,
SpringApplicationRunListenerslisteners, ApplicationArgumentsapplicationArguments, BannerprintedBanner) {
    ......
// 加载资源load(context, sources.toArray(newObject[0]));
listeners.contextLoaded(context);
}


在这个方法中,会做一些准备工作,包括初始化容器上下文、设置环境、加载资源等。


加载资源

上面的代码中,又调用了一个很关键的方法 —— load ()。这个 load () 方法真正的作用是去调用 BeanDefinitionLoader 类的 load () 方法。源码如下:


classBeanDefinitionLoader {
    ......
intload() {
intcount=0;
for (Objectsource : this.sources) {
count+=load(source);
  }
returncount;
    }
privateintload(Objectsource) {
Assert.notNull(source, "Source must not be null");
if (sourceinstanceofClass<?>) {
returnload((Class<?>) source);
  }
if (sourceinstanceofResource) {
returnload((Resource) source);
  }
if (sourceinstanceofPackage) {
returnload((Package) source);
  }
if (sourceinstanceofCharSequence) {
returnload((CharSequence) source);
  }
thrownewIllegalArgumentException("Invalid source type "+source.getClass());
    }
    ......
}


可以看到,load () 方法在加载 Spring 中各种资源。其中我们最熟悉的就是 load ((Class<?>) source) 和 load ((Package) source) 了。一个用来加载类,一个用来加载扫描的包。


load ((Class<?>) source) 中会通过调用 isComponent () 方法来判断资源是否为 Spring 容器管理的组件。 isComponent () 方法通过资源是否包含 @Component 注解(@Controller、@Service、@Repository 等都包含在内)来区分是否为 Spring 容器管理的组件。


而 load ((Package) source) 方法则是用来加载 @ComponentScan 注解定义的包路径。


3.5 小结

我们知道,Spring 是一个容器,我们喜欢它的一个重要原因就是它帮我们把 Bean 进行了统一的管理。Bean 的创建与销毁都由 Spring 来完成,而我们只需要关注使用,这也是 Spring IoC 的核心工作内容。

到此,Spring 真正开始开展 Bean 管理的工作了,prepareContext () 方法把所有需要管理的 Bean 统计出来,在后面的 refreshContext () 方法中会进行更进一步的操作。 refreshContext() 方法和自动配置关系紧密。


相关文章
|
4月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
448 1
|
19天前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
5月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
421 7
|
6月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1622 1
|
5月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
394 70
|
4月前
|
供应链 JavaScript BI
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
这是一款专为小微企业打造的 SaaS ERP 管理系统,基于 SpringBoot+Vue+ElementUI+UniAPP 技术栈开发,帮助企业轻松上云。系统覆盖进销存、采购、销售、生产、财务、品质、OA 办公及 CRM 等核心功能,业务流程清晰且操作简便。支持二次开发与商用,提供自定义界面、审批流配置及灵活报表设计,助力企业高效管理与数字化转型。
451 2
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
|
3月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
265 0
|
6月前
|
小程序 Java 关系型数据库
weixin117新闻资讯系统设计+springboot(文档+源码)_kaic
本文介绍了一款基于微信小程序的新闻资讯系统,涵盖其开发全过程。该系统采用Java的SSM框架进行后台管理开发,使用MySQL作为本地数据库,并借助微信开发者工具确保稳定性。管理员可通过个人中心、用户管理等功能模块实现高效管理,而用户则能注册登录并查看新闻与视频内容。系统设计注重可行性分析(技术、经济、操作),强调安全性与数据完整性,界面简洁易用,功能全面,极大提升了信息管理效率及用户体验。关键词包括基于微信小程序的新闻资讯系统、SSM框架和MYSQL数据库。
|
6月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
6月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
184 4

推荐镜像

更多
  • DNS