SpringBoot源码分析(二)启动原理
Springboot的jar启动方式,是通过IOC容器启动 带动了Web容器的启动
而Springboot的war启动方式,是通过Web容器(如Tomcat)的启动 带动了IOC容器相关的启动
一、不可不说的Web容器(如Tomcat)
不管是jar启动还是war包启动,都绕不开web容器相关。先了解这个怎么工作的,以Tomcat为例,
看看Springboot 怎么来自动装配tomcat 相关的组件?
1.1 相关类
相关包org.springframework.boot.autoconfigure.web,在springboot的自动配置包的web下(自动配置功能都在这个autoconfigure包下)。

embedded(内嵌)里面四个类一个A四B,一个:
EmbeddedWebServerFactoryCustomizerAutoConfiguration(内嵌web容器工厂自定义定制器装配类)
四个具体容器相关:
JettyWebServerFactoryCustomizer、NettyWebServerFactoryCustomizer、TomcatWebServerFactoryCustomizer、UndertowWebServerFactoryCustomizer
一个自动配置类+四个常用web容器定制器
1.2.工作流程
以Tomcat定制器切入,断点落在构造器上,启动。
总结出它的工作流程:
1.启动=》2.createWebServer=》
3.拿TomcatServletWebServerFactory(tomcatWeb容器工厂)=》
4.拿WebServerFactoryCustomizer(工厂定制器)
也就是拿工厂定制器获取工厂,再拿工厂获取web容器,这么个流程
1.3.具体工作
可以仔细看下相关工厂是如何配置创建容器运行的
TomcatServletWebServerFactory的创建Tomcat方法
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
创建Tocmat类并设置相关这些组件,应该很熟悉(以后出Tomcat源码分析)。
TomcatWebServerFactoryCustomizer的customize定制方法,通过类serverProperties配置文件设置工厂的属性
二、SpringBoot的jar启动方式
来自:
public static void main(String[] args) {
SpringApplication.run(StudySpringbootApplication.class, args);
}
打开源码:
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
其实两步,一步创建SpringApplication ,一步run运行。
创建类做的事情比较简单,主要包括判断web应用类型、用SpringFactories技术从 spring.factories 文件里获取ApplicationContextInitializer 对应类和ApplicationListener,最后获取当前应用的启动类的类对象。
2.1 run方法
public ConfigurableApplicationContext run(String... args) {
//StopWatch是记录时间的工具,为了打印那句SpringBoot启动耗时的
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//系统设置,在缺失显示屏、鼠标或者键盘时也让这个java应用相关正常工作
configureHeadlessProperty();
//去meta-info/spring.factories中获取SpringApplicationRunListener 监听器(事件发布监听器)
SpringApplicationRunListeners listeners = getRunListeners(args);
//发布容器 starting事件(for循环一个个调用,通过spring的事件多播器)
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备容器环境
//1:获取或者创建环境
//2:把命令行参数设置到环境中
//3:通过监听器发布环境准备事件
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//配置是否跳过搜索BeanInfo类,默认忽略跳过
configureIgnoreBeanInfo(environment);
//打印控制台那个SpringBoot图标
Banner printedBanner = printBanner(environment);
//根据类型(servlet或者reactive?)创建应用上下文ApplicationContext
context = createApplicationContext();
//到spring.factoris文件里拿springboot异常报告类的集合
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备环境
//1:把应用上下文ApplicationContext环境设置到容器中
//2:循环调用AppplicationInitnazlier 进行容器初始化工作
//3:发布 容器上下文准备 完成事件
//4:注册关于springboot特定特性的相关单例Bean
//5:BeanDefinitionLoader加载资源源码,将启动类注入容器
//6:发布 容器上下文加载 完毕事件
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//IOC容器refresh,见以前IOC源码分析
refreshContext(context);
//springboot2.x已经改成空方法,以前里面是后面的callRunners
afterRefresh(context, applicationArguments);
//计时(耗时统计)停止
stopWatch.stop();
//打印那句springboot在多少秒内启动了
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//发布容器启动事件
listeners.started(context);
//运行 ApplicationRunner 和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//发布容器运行事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
其实大多是准备、工具、事件等,最核心的还是里面的refreshContext(context);带动了IOC容器启动
2.2 refreshContext(context)
其实大部分内容在之前IOC容器源码写过,唯一的区别在于:
SpringIOC的refresh方法里的onRefresh方法是空的,而SpringBoot继承重写了这个方法!
SpringBoot的onRefresh:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
SpringBoot里应用上下文是用的新的ServletWebServerApplicationContext类(更具体实现之一是AnnotationConfigServletWebServerApplicationContext)
这里就开始和上面说过的Web容器相关知识衔接上了,这里进行的Web容器(Tomcat)的创建运行!
createWebServer:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
//...
}
}
initPropertySources();
}
这里就是判断有没有Server以及环境,没有的话就获取web容器制造工厂,最后通过工厂获取Tomcat赋值。
实际上获取Tomcat创建的时候,此时构造器最后的代码就是启动,TomcatWebServer类构造器如下:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
TomcatWebServer的initialize:
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
this.tomcat.start();
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
}
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
最终在IOC 容器中的 org.springframework.context.support.AbstractApplicationContext的refresh 的
onReFresh方法带动了Tomcat启动
三、SpringBoot的war包启动方式
@SpringBootApplication
public class StudySpringbootApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(StudySpringbootApplication.class);
}
}
Springboot的war启动方式,是通过Web容器(如Tomcat)的启动 带动了IOC容器相关的启动
3.1 Tomcat加载war
要说Tomcat怎么加载war包就不得不从servlet3.0的特性说起:
1.web应用启动,会创建当前Web应用导入jar包中的 ServletContainerInitializer类的实例
2.ServletContainerInitializer 类必须放在jar包的 META-INF/services目录下,文件名称为javax.servlet.ServletContainerInitializer
3.文件的内容指向ServletContainerInitializer实现类的全路径
4.ServletContainerInitializer实现类使用@HandlesTypes注解, 在我们应用启动的时候,加载注解指定的的类
3.2 Spring中的ServletContainerInitializer
Spring中实现ServletContainerInitializer的类是SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) {
//创建保存需要加载的类的集合
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
//判断需要加载的类不是接口不是抽象类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//通过反射创建实例并且加入到集合中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
//...
}
}
}
}
if (initializers.isEmpty()) {
return;
}
AnnotationAwareOrderComparator.sort(initializers);
//循环调用集合中的感兴趣类对象的onstartup的方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
总结:HandlesTypes指定了WebApplicationInitializer类,并在onStartup方法中,创建这些需要加载的类的实例,并且循环调用他们的onStartup方法。
3.2 工作过程
1.Tomcat启动,war包应用的jar包里找ServletContainerInitializer 文件,然后找到spring-web-5.1.2.RELEASE.jar这个jar包里的META-INF\services\javax.servlet.ServletContainerInitializer 文件指向自己实现的SpringServletContainerInitializer并执行它
2.将@HandlesTypes标注的类(WebApplicationInitializer)都传入到 onStartup()的方法中Set<Class<?>>参数中
,通过 ReflectionUtils.accessibleConstructor(waiClass).newInstance());,为这些类创建实例
3.调用WebApplicationInitializer的onStartup方法
4.而Springboot启动类继承了SpringBootServletInitializer(实现了接口WebApplicationInitializer)
5.而我们的启动类StudySpringbootApplication没有重写onStartup,调的SpringBootServletInitializer的onStartup
6.而SpringBootServletInitializer的onStartup方法调了我们重写的configure方法,加载启动。
4.1 实战调试细节
@SpringBootApplication
public class StudySpringbootApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(StudySpringbootApplication.class);
}
}
可以把断点打到第六行里builder.sources,通过断点一看调用栈和代码,逻辑就全出来了:
StudySpringbootApplication.configure <<==== 父类SpringBootServletInitializer(主类继继承的这个类).createRootApplicationContext
父类SpringBootServletInitializer.createRootApplicationContext:
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//创建spring应用的构建器
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
//设置环境
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用我们自己启动类上的confiure方法 传入我们自己的主启动类
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
//调用我们类上的run方法
return run(application);
}
注意重点是 调用了自己的方法(传入主类)和 run方法
run源码:
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
继续打开run源码:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
是不是很熟悉,和jar启动的run IOC一样!所以最终还是殊途同归,还是走了application.run()方法,走了IOC容器启动Refresh!
SpringBoot源码分析(二)启动原理的更多相关文章
- 带着萌新看springboot源码12(启动原理 下)
先继续接上一篇,那个启动原理还有一点没说完. 6. afterRefresh(context, applicationArguments); 看这个名字就知道,应该就是ioc容器刷新之后的一些操作了, ...
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- 深入源码分析SpringMVC底层原理(二)
原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- Kafka源码分析及图解原理之Producer端
一.前言 任何消息队列都是万变不离其宗都是3部分,消息生产者(Producer).消息消费者(Consumer)和服务载体(在Kafka中用Broker指代).那么本篇主要讲解Producer端,会有 ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- Appium Server 源码分析之启动运行Express http服务器
通过上一个系列Appium Android Bootstrap源码分析我们了解到了appium在安卓目标机器上是如何通过bootstrap这个服务来接收appium从pc端发送过来的命令,并最终使用u ...
- Appium Android Bootstrap源码分析之启动运行
通过前面的两篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>和<Appium Android Bootstrap源码分析之命令解析 ...
- php中foreach源码分析(编译原理)
php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...
- Springboot源码分析之项目结构
Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...
随机推荐
- 杂记:VMware中为mac虚拟机扩容
之前在VMware中安装Mac虚拟机时,硬盘选的是默认的40G,后来用的过程中随着软件的安装,特别是安装完Xcode和QT5.9之后,可用空间只剩不到3G,每次开机之后都会提醒空间不足,需要清理空间, ...
- Dream权限追踪系统<=2.0.1 重安装漏洞
在./install/install.php中 if(file_exists('lock.txt')){ echo '系统已安装,请不要重复安装!如需安装,请删除install文件夹下的lock.tx ...
- 关于排序原生js实现
内外排序的区别 排序可以分为两种:内排序和外排序.当数据量不算太大的时候,计算可以在内存中完成的排序称为内排序.当数据量特别大,计算的数据,需要分多次进入内存中进行计算的排序称之为外排序 插入排序 直 ...
- 安卓权威编程指南 挑战练习:实现高效RecyclerView刷新
Adapter的notifyDataSetChanged方法会通知RecyclerView刷新全部的可见列表项. 在CriminalIntent应用里,这个方法不够高效,我们知道,返回CrimeLis ...
- 如何正确使用redis分布式锁
前言 笔者在公司担任技术面试官,在笔者面试过程中,如果面试候选人提到了reids分布式锁,笔者都会问一下redis分布式锁的知识点,但是令笔者遗憾的是,该知识点十个人中有九个人都答得不清楚,或者回 ...
- Asp.net Core MVC(四)
上一篇说的是asp.net mvc核心UseMvc的过程,末尾想捋一下asp.net核心的路由流转过程,现在看来还是要准备下一个代码,熟悉了代码,那么整个流转过程就通了〜 不多说,今儿先看下,Rout ...
- javascript简单轮播图
**轮播图实现原理: 通过多张图片平铺,用overflow:hidden只显示一张图片.其他的隐藏,无缝滚动用定时器改变元素的left值让图片呈现左右滚动的效果.** HTML布局和内容: 1.容器c ...
- zookeeper和dubbo安装与搭建(2)
Zookeeper+Dubbo安装与搭建(2) (原创:黑小子-余) 一.环境配置:zookeeper3.6.0 + dubbo3.5.4 + maven3.6.1 + jdk1.8 + tomcat ...
- .NET异步程序设计之async&await
目录 0.背景引入 1.async和await基本语法 2.异步方法的执行顺序 3.取消一个异步操作 4.同步和异步等待任务 5.异步操作中的异常处理 6.多线程和异步的区分 7. 在 .NET MV ...
- C# BASS音频库 + 频谱基本用法
效果图: 使用了 BASS.dll. BASS.NET.dll 和 PeakMeterCtrl.dll 前面两个负责播放 最后一个负责绘制频谱,本文重点讲的是频谱部分,播放音频部分注意一点 ...