传统的Spring MVC工程部署时需要将WAR文件放置在servlet容器的文档目录内,而Spring Boot工程使用嵌入式servlet容器省去了这一步骤,本文分析Spring Boot中嵌入式servlet容器的创建和启动过程。
刷新应用上下文
Spring Boot的启动过程一文指出在Spring Boot工程中,Web环境下默认创建AnnotationConfigEmbeddedWebApplicationContext类型的应用上下文,它的刷新方法定义在它的父类EmbeddedWebApplicationContext中,相关代码如下:
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseEmbeddedServletContainer();
throw ex;
}
}
EmbeddedWebApplicationContext类重写的refresh方法在内部调用了基类AbstractApplicationContext的refresh方法,其代码如下所示:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
// 省略一些代码
}
}
- refresh方法可以看成是模板方法,子类可以重写prepareRefresh、onRefresh和finishRefresh等方法。
EmbeddedWebApplicationContext类重写的onRefresh和finishRefresh方法如下:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
@Override
protected void finishRefresh() {
super.finishRefresh();
EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
if (localContainer != null) {
publishEvent(
new EmbeddedServletContainerInitializedEvent(this, localContainer));
}
}
- onRefresh方法首先调用基类的onRefresh方法,然后创建嵌入式servlet容器;
- finishRefresh方法首先调用基类的finishRefresh方法,然后启动嵌入式servlet容器。
创建嵌入式servlet容器
EmbeddedWebApplicationContext类的createEmbeddedServletContainer方法创建嵌入式servlet容器,代码如下:
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
- 嵌入式servlet容器由EmbeddedServletContainer接口抽象,该接口的实现类有TomcatEmbeddedServletContainer、JettyEmbeddedServletContainer和UndertowEmbeddedServletContainer,分别包装了嵌入式Tomcat、Jetty和Undertow;
- EmbeddedServletContainerFactory接口用于实际创建嵌入式servlet容器,该接口的实现类有TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory和UndertowEmbeddedServletContainerFactory,分别用于创建上述三种嵌入式servlet容器;
- getEmbeddedServletContainerFactory方法从当前应用上下文中取得唯一的EmbeddedServletContainerFactory类型的bean,若有多个则报错。使用默认的自动配置时,该bean一定是TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory或者UndertowEmbeddedServletContainerFactory中的一个。
ServletContextInitializer接口
在上述创建嵌入式servlet容器的过程中,EmbeddedServletContainerFactory接口方法的实参是getSelfInitializer方法的返回值,类型是ServletContextInitializer。ServletContextInitializer接口用于动态配置ServletContext,只有一个回调方法onStartup在容器启动时被调用。
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
ServletContextInitializer的类层次结构如下图所示,可见ServletRegistrationBean和FilterRegistrationBean都实现了该接口,它们分别向容器添加新的servlet和过滤器。
配置ServletContext
getSelfInitializer方法的代码如下,只是调用了selfInitialize方法。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
容器启动时具体的配置动作由selfInitialize方法完成,其代码如下:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareEmbeddedWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
该方法主要做了以下工作:
- prepareEmbeddedWebApplicationContext方法将应用上下文设置到ServletContext的属性中,过程与Spring MVC的启动过程一文中分析的ContextLoader初始化根应用上下文非常相似;
protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) { Object rootContext = servletContext.getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (rootContext != null) { if (rootContext == this) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ServletContextInitializers!"); } return; } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring embedded WebApplicationContext"); try { servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); if (logger.isDebugEnabled()) { logger.debug( "Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } setServletContext(servletContext); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - getStartupDate(); logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
- 调用其他ServletContextInitializer的回调方法,如ServletRegistrationBean和FilterRegistrationBean分别向容器添加新的servlet和过滤器。
启动嵌入式servlet容器
EmbeddedWebApplicationContext类的startEmbeddedServletContainer方法启动先前创建的嵌入式servlet容器,在内部调用EmbeddedServletContainer的start接口方法:
private EmbeddedServletContainer startEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if (localContainer != null) {
localContainer.start();
}
return localContainer;
}
嵌入式Tomcat
在Spring Boot中,嵌入式Tomcat由TomcatEmbeddedServletContainer类包装,该类的实例创建于TomcatEmbeddedServletContainerFactory。
TomcatEmbeddedServletContainerFactory类实现了EmbeddedServletContainerFactory接口,实现的接口方法如下:
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatEmbeddedServletContainer(tomcat);
}
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
- 首先新建Tomcat实例,然后设置工作目录,最后绑定并自定义Connector、Engine和Context等Tomcat组件,关于这些组件的功能可以参考笔者以前的Tomcat分析系列;
- getTomcatEmbeddedServletContainer方法返回包装有嵌入式Tomcat的TomcatEmbeddedServletContainer实例。