SpringBoot使用外置Servlet容器和原理分析
1、使用外部servlet容器的步骤
1)、maven项目打包方式为war
<packaging>war</packaging>
2)、将嵌入式Servlet容器的打包方式指定为provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
3)、必须编写一个SpringBootServletInitializer的子类,并重写configure方法,如下,其中ExtTomcatApplication是SpringBoot的主程序类
package com.dxy.springboot;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExtTomcatApplication.class);
}
}
4)、启动外置Servlet容器即可
2、原理分析
1)、在SpringBoot打包成jar包的情况下是如何启动的?启动main方法,初始化IOC容器,创建嵌入式Servlet容器并启动,可参考另外一篇博文SpringBoot启动流程分析;当打包成war的时候是启动外置Servlet容器的,然后服务器启动SpringBoot应用【SpringBootServletInitializer】,最后再启动ioc容器。
2)、为何会有上面描述的启动流程,这里就需要了解一下servlet3.0的一个规范
① 服务器启动web应用的时候会去该应用下的所有的jar包中找ServletContainerInitializer的实例
② 如何找到这些实例呢?这些实例的全类名都存到jar包的META-INF/services目录下名为
javax.servlet.ServletContainerInitializer文件中,如下图,在spring-web-5.2.2.RELEASE.jar包下就有该文件
文件内容是org.springframework.web.SpringServletContainerInitializer
③ 打开该类,我们一探究竟
//该注解标注的接口的实现类会被实例化
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//将所有的@HandlesTypes标注的接口的实现类的class对象保存到Set集合中
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
//创建了一个@HandlesTypes注解标注类型的List用于保存所有的实例
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//判断如果不是接口或者抽象类,则进行实例化并添加到List中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
//运行上面实例化的所有对象的onStartup方法
initializer.onStartup(servletContext);
}
}
}
根据上面的servlet3.0规范中的第二段结合上面的源代码可知,在该类的onStartup方法中会将所有@HandlesTypes(WebApplicationInitializer.class)注解标注的接口(这里是WebApplicationInitializer)的实现类进行实例化,并运行他们的onStartup方法,请结合上面代码中的注解进行理解。
④ 上面类中@HandlesTypes标注的WebApplicationInitializer又是何方神圣,它其实就是我们在最开头讲述的SpringBootServletInitializer 的父接口,也即是上面要实例化的就包括我们自定义的这个类。
⑤ 上面说了会运行所有实例化的WebApplicationInitializer子类的onStartup方法,那我们就需要看看这个方法做了些什么,我们自动的类中没有该方法,那就应该在它的父类SpringBootServletInitializer中
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
//调用下面的createRootApplicationContext方法
WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
public void contextInitialized(ServletContextEvent event) {
}
});
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//创建一个SpringApplication的构建器
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//运行我们自定义类覆盖的方法,从而便可以获得该SpringBoot的主程序类
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//通过上面的builder构建出一个SpringApplication对象
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
//运行SpringApplication的run方法启动整个SpringBoot应用,就回到了SpringBoot的启动流程
return this.run(application);
}
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext)application.run(new String[0]);
}
⑥ 通过调用SpringApplication的run方法就启动了整个SpringBoot应用,也就回到了SpringBoot的启动流程中,最后服务启动完成。