前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【SpringBoot源码解析】第三章:SpringBoot通过打成war包的方式是如何启动的

【SpringBoot源码解析】第三章:SpringBoot通过打成war包的方式是如何启动的

原创
作者头像
诺浅
修改2020-08-21 17:47:21
8660
修改2020-08-21 17:47:21
举报
文章被收录于专栏:工具使用工具使用

缘起

在前面几章的讲解中,我们知道,当我们执行以下代码时,springboot会启动一个内置的tomcat,并且加载对应的starter.那么如果我们不采用java -jar的方式启动springboot的应用,他也就没有去执行run方法,那么他又是如何做到自动装配的呢?

SpringBoot通过war的方式是如何启动的

关于SPI

在说这些之前,我们先要了解一个东西,SPI。关于SPI可以去了解我的另一篇文章

Java SPI 机制详解

SPI在springboot中的应用

我们看spring-web这个项目的spi文件javax.servlet.ServletContainerInitializer

image.png
image.png

文件内容如下

代码语言:txt
复制
 org.springframework.web.SpringServletContainerInitializer

SpringServletContainerInitializer代码如下

代码语言:txt
复制
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}

可以看到这个类继承了一个叫ServletContainerInitializer的接口

代码语言:txt
复制
public interface ServletContainerInitializer {
	public void onStartup(Set<Class<?>> c, ServletContext ctx)
        	throws ServletException; 
}

这个接口是干嘛的呢?ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架。容器启动阶段依据java spi获取到所有ServletContainerInitializer的实现类,然后执行其onStartup方法.

也就是说,我们把 ServletContainerInitializer的实现类写在 META-INF / services / javax.servlet.ServletContainerInitializer 文件中,那么Tomcat等容器启动的时候就会去调用所有实现类的onStartup方法。

@HandlesTypes注解是干嘛的呢?

SpringServletContainerInitializer就是ServletContainerInitializer的实现类,可以看到SpringServletContainerInitializer加上了一个@HandlesTypes(WebApplicationInitializer.class)的注解,这个注解的作用就是容器启动的时候调用实现类的onStartup方法的时候,会把注解中标注的接口的实现类当做参数传递进去。

我们看SpringServletContainerInitializeronStartup方法,在容器启动的时候会调用这个方法,同时set集合参数webAppInitializerClasses即为@HandlesTypes中标注的WebApplicationInitializer的实现类

代码语言:txt
复制
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
	//省略其余代码
	for (WebApplicationInitializer initializer : initializers) {
		//依次回调参数实现类的onStartup方法
		initializer.onStartup(servletContext);
	}
}

最后一段代码initializer.onStartup(servletContext);就是把所有的WebApplicationInitializer的实现类的onStartup方法调用一遍。我们看看这个类的所有实现类

image.png
image.png

看到实现类中有一个SpringBootServletInitializer,这个类是我们要重点关注的对象,先来看看这个类的注释

在这里插入图片描述
在这里插入图片描述

也就是说这个类是当我们以war包的方式让外部tomcat运行时才需要关注的类。

我们接着看这个类的onStartup方法

代码语言:txt
复制
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
	WebApplicationContext rootAppContext = createRootApplicationContext(
			servletContext);
	if (rootAppContext != null) {
		servletContext.addListener(new ContextLoaderListener(rootAppContext) {
			@Override
			public void contextInitialized(ServletContextEvent event) {
				// no-op because the application context is already initialized
			}
		});
	}
}

createRootApplicationContext方法

代码语言:txt
复制
protected WebApplicationContext createRootApplicationContext(
		ServletContext servletContext) {
	//其余代码略
	return run(application);
}

run方法

代码语言:txt
复制
protected WebApplicationContext run(SpringApplication application) {
	return (WebApplicationContext) application.run();
}

可以看到最终上诉会调用到SpringApplication的无参run方法,那么到了这一步,就跟我们通过main方法启动是一个道理了

代码语言:txt
复制
public static void main(String[] args) {
	SpringApplication.run(HppaApplication.class, args);
}

总结

SpringBoot通过打成war包的方式运行,其本质上是利用了Servlet3.0规范中的Tomcat启动时会去调用ServletContainerInitializer接口的onStartup方法,同时把使用类注解@HandlesTypes中标注的接口的实现类作为参数传入到onStartup中,并依次调用其实现类的onStartup方法。而SpringServletContainerInitializer实现了ServletContainerInitializer,同时标注了@HandlesTypes(WebApplicationInitializer.class)

代码语言:txt
复制
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}

那么也就是说,Tomcat启动时,最终会去调用WebApplicationInitializer的实现类的onStartup方法,而SpringBootServletInitializer实现了WebApplicationInitializer

代码语言:txt
复制
public abstract class SpringBootServletInitializer implements WebApplicationInitializer

那也就是最终会调用SpringBootServletInitializeronStartup方法,而这个onStartup方法最终其实是调用了application.run(),也就类似于我们通过Main方法启动了。为了方便理解,我画了下图

在这里插入图片描述
在这里插入图片描述

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缘起
  • SpringBoot通过war的方式是如何启动的
    • 关于SPI
      • SPI在springboot中的应用
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档