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

时间:2022-07-23
本文章向大家介绍【SpringBoot源码解析】第三章:SpringBoot通过打成war包的方式是如何启动的,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

缘起

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

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

关于SPI

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

Java SPI 机制详解

SPI在springboot中的应用

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

image.png

文件内容如下

 org.springframework.web.SpringServletContainerInitializer

SpringServletContainerInitializer代码如下

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}

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

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的实现类

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

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

在这里插入图片描述

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

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

@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方法

protected WebApplicationContext createRootApplicationContext(
		ServletContext servletContext) {
	//其余代码略
	return run(application);
}

run方法

protected WebApplicationContext run(SpringApplication application) {
	return (WebApplicationContext) application.run();
}

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

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)

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//省略其余代码
}

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

public abstract class SpringBootServletInitializer implements WebApplicationInitializer

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

在这里插入图片描述