一、内置Servlet容器和外置Servlet容器的对比
内置:将应用打成jar包,项目启动时执行SpringBoot主配置类的main方法,启动IOC容器,创建嵌入式的Servlet容器并启动
外置:将应用打成war包,先启动外置的Servlet服务器(如tomcat),通过外置的服务器启动SpringBoot应用(将SpringBoot应用的主配置类作为参数传入SpringBootServletInitializer的实现类的configure方法中),再启动IOC容器
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//SpringbootWarApplication是该应用的主配置类
return application.sources(SpringbootWarApplication.class);
}
}
二、原理
在Servlet3.0规范中有一个规范:服务器启动(web应用启动)时会创建当前web应用里面每一个jar包下的ServletContainerInitializer接口的实例,每个jar包下该接口的实现类会在该jar包的META-INF/services文件夹下名为javax.servlet.ServletContainerInitializer的文件中指定,该文件中指定的就是ServletContainerInitializer接口的实现类的全类名。在该实现类中还可以使用@HandlesTypes加载我们感兴趣的类,把我们感兴趣的类在加入到容器中。
而在SpringBoot的web模块下就有一个javax.servlet.ServletContainerInitializer文件:
该文件中指定的实现类的源码如下:
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)waiClass.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
在这段代码的第一行就使用@HandlesTypes将WebApplicationInitializer.class作为我们感兴趣的类引入了,我们可以看到在最后会调用该类的onStartup(),而我们在创建SpringBoot项目选择将其打成war时在项目中会有一个继承于SpringBootServletInitializer的类,SpringBootServletInitializer其实是WebApplicationInitializer的子类,这样的话在外置的Servlet容器启动时就会运行SpringBootServletInitializer的子类的onStartup(),在SpringBootServletInitializer的子类的configure()中我们又传入了SpringBoot应用的主配置类,如此就可以在启动Servlet容器时运行SpringBoot主配置类的main(),从而启动SpringBoot应用了。
三、流程
1、启动Tomcat
2、在web模块下的\META-INF\services\javax.servlet.ServletContainerInitializer文件中扫描到ServletContainerInitializer的实现类org.springframework.web.SpringServletContainerInitializer
3、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer类型的类都传入到onStartup方法的Set中,为WebApplicationInitializer类型所有类创建实例,而SpringBootServletInitializer也是WebApplicationInitializer的一个子类
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootWarApplication.class);
}
}
4、每一个WebApplicationInitializer的实现类都会调用自己的onStartup(),相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
5、SpringBootServletInitializer实例执行onStartup的时候会调用createRootApplicationContext()创建容器:
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
//创建容器
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");
}
}
在创建容器的时候会启动SpringBoot应用:
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
// 创建SpringApplicationBuilder
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(AnnotationConfigEmbeddedWebApplicationContext.class);
// 调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
// 使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.getSources().add(this.getClass());
}
Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return this.run(application);
}
6、Spring应用启动就会创建IOC容器,接着就会执行刷新容器等操作
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
new FailureAnalyzers(context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
总之在Servlet容器外置时是先启动的外置Servlet容器,再通过外置Servlet容器启动SpringBoot应用
另外根据原理可知在我们手动创建一个打包方式为war的SpringBoot项目并使用外置的tomcat启动时只需要创建一个SpringBootServletInitializer的子类即可