前言
大家都知道SpringBoot有内置Tomcat的功能,在启动原理中有介绍SpringBoot内置Tomcat的原理。那么如果我们不像使用SpringBoot的内置Tomcat,想用自己的web容器怎么操作。springboot支持默认三大容器,Tomcat,Jetty,Undertow,可以在pom包中通过依赖控制。
ServletWebServerFactoryAutoConfiguration注解上

这里就体现了SpringBoot内置容器有哪些
使用Undertow内置容器
pom
排除tomcat依赖,增加undertow依赖
<!--排除tomcat依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

使用jetty内置容器
pom
排除tomcat依赖,增加jetty依赖
<!--排除tomcat依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用jetty容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

如何使用外部容器
- 更改打包方式,排除tomcat pom依赖,这时候也不要加一些UnderTow或Jetty的包
- 编写Tomcat启动类,找到SpringBoot主程序
- 在tomcat容器中运行
pom
排除依赖是为了让ServletWebServerFactoryAutoConfiguration不在自动装配任何一个容器类,但这里是可以不用排除依赖其实也是可以使用外部容器的,从代码中可以看出如果使用了外部servlet会优先使用外部sevlet而不会使用自动装配的sevlet。
<packaging>war</packaging>
<!--排除tomcat依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
编写Tomcat启动类
实现SpringBootServletInitializer重写configure方法
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
}
装载到tomcat容器

使用外部容器原理
- Tomcat是使用SPI机制找到SpringServletContainerInitializer
- 在其上标注了注解@HandlesTypes(WebApplicationInitializer.class)
- 通过@HandlesTypes找到WebApplicationInitializer的所有实现类注入到onStartup方法的webAppInitializerClasses参数中
- 然后执行WebApplicationInitializer实现类中的onStartup方法,初始化ContextLoaderListener和DispatcherServlet和启动Spring
SPI机制SpringServletContainerInitializer
在META-INF/services文件夹中找到javax.servlet.ServletContainerInitializer, 这个文件里面是ServletContainerInitializer的实现类SpringServletContainerInitializer,创建它的实例调用onstartUp

HandlesTypes原理
HandlesTypes是在tomcat–ContextConfig#processServletContainerInitializers方法中注入的,主要过程如下
- 通过SPI机制找到所有的ServletContainerInitializer的实现类,放到ServletContainerInitializer集合下
- 如果在ServletContainerInitializer实现类上标注了HandlesTypes注解就继续,否则结束。找到之后这里只存储了注解的类名,这时候并没有HandlesTypes注解的接口所实现的类封装起来
- 通过ContextConfig#processAnnotationsStream方法中的ClassParser从classes中获取到类然后在checkHandlesTypes方法中封装进initializerClassMap中
- 在ContextConfig#webConfig方法中通过context#addServletContainerInitializer方法把initializerClassMap值放到了StandardContext中的initializers中
- 在StandardContext#startInternal中调用ServletContainerInitializer#onStart方法
SpringServletContainerInitializer启动原理
上面基本上是Tomcat和servlet的一些原理,下面才开始进入到Spring的一个底层了。
从上面我们可以看出SpringServletContainerInitializer会通过SPI进行运行,而且会把WebApplicationInitializer的所有实现类注入到webAppInitializerClasses方法中,那么我们就可以自定义WebApplicationInitializer实现类进行一些操作了。这里我们就加了一个Tomcat启动类,就是上面加的TomcatStartSpringBoot,在这个启动类里面我们配置类SpringBoot启动类,以便于我们启动Spring容器。
SpringServletContainerInitializer这个类其实也没什么就是把WebApplicationInitializer的实现类的onStartup方法挨个调一遍。

SpringBootServletInitializer启动原理
我们实现的Tomcat启动类就是继承的SpringBootServletInitializer,我们只是重写了config方法,通过SpringServletContainerInitializer发现我们一定会调用SpringBootServletInitializer#onStartup方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
// ===========核心方法============
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
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
onStart方法中只有createRootApplicationContext比较重要
SpringBootServletInitializer#createRootApplicationContext
- 构建SpringApplicationBuilder对象,创建SpringApplication对象
- 通过重写configure方法把SpringBoot启动类传到build中,通过build创建的SpringApplication调用run方法,成功来到SpringBoot的启动方法。
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
// new一个SpringApplicationBuilder对象,用其生产SpringApplication
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
// 将SpringBoot启动类传到build中构建SpringApplication对象
//通过configure()方法扩展
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()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
// 运行SpringApplication run方法等价于SpringBoot启动类的SpringApplication.run(MyApplication.class);
return run(application);
}
总结
外部容器启动无非就是通过SPI和HandlesType找到我们自定义的启动类,通过重写SpringBootServletInitializer#config方法把SpringBoot启动类传到SpringApplication中,通过SpringApplication#run创建容器。