傳統(tǒng)的Spring MVC工程部署時需要將WAR文件放置在servlet容器的文檔目錄內,而Spring Boot工程使用嵌入式servlet容器省去了這一步驟屋摔,本文分析Spring Boot中嵌入式servlet容器的創(chuàng)建和啟動過程薛匪。
刷新應用上下文
Spring Boot的啟動過程一文指出在Spring Boot工程中四敞,Web環(huán)境下默認創(chuàng)建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方法红碑,然后創(chuàng)建嵌入式servlet容器;
- finishRefresh方法首先調用基類的finishRefresh方法,然后啟動嵌入式servlet容器析珊。
創(chuàng)建嵌入式servlet容器
EmbeddedWebApplicationContext類的createEmbeddedServletContainer方法創(chuàng)建嵌入式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接口抽象,該接口的實現(xiàn)類有TomcatEmbeddedServletContainer忠寻、JettyEmbeddedServletContainer和UndertowEmbeddedServletContainer惧浴,分別包裝了嵌入式Tomcat、Jetty和Undertow奕剃;
- EmbeddedServletContainerFactory接口用于實際創(chuàng)建嵌入式servlet容器衷旅,該接口的實現(xiàn)類有TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory和UndertowEmbeddedServletContainerFactory纵朋,分別用于創(chuàng)建上述三種嵌入式servlet容器柿顶;
- getEmbeddedServletContainerFactory方法從當前應用上下文中取得唯一的EmbeddedServletContainerFactory類型的bean,若有多個則報錯操软。使用默認的自動配置時嘁锯,該bean一定是TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory或者UndertowEmbeddedServletContainerFactory中的一個聂薪。
ServletContextInitializer接口
在上述創(chuàng)建嵌入式servlet容器的過程中家乘,EmbeddedServletContainerFactory接口方法的實參是getSelfInitializer方法的返回值,類型是ServletContextInitializer藏澳。ServletContextInitializer接口用于動態(tài)配置ServletContext仁锯,只有一個回調方法onStartup在容器啟動時被調用。
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
ServletContextInitializer的類層次結構如下圖所示翔悠,可見ServletRegistrationBean和FilterRegistrationBean都實現(xiàn)了該接口扑馁,它們分別向容器添加新的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方法啟動先前創(chuàng)建的嵌入式servlet容器,在內部調用EmbeddedServletContainer的start接口方法:
private EmbeddedServletContainer startEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if (localContainer != null) {
localContainer.start();
}
return localContainer;
}
嵌入式Tomcat
在Spring Boot中乱投,嵌入式Tomcat由TomcatEmbeddedServletContainer類包裝咽笼,該類的實例創(chuàng)建于TomcatEmbeddedServletContainerFactory。
TomcatEmbeddedServletContainerFactory類實現(xiàn)了EmbeddedServletContainerFactory接口戚炫,實現(xiàn)的接口方法如下:
@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實例。