我们知道,在使用Spring boot搭建web工程时,我们不需要自己搭建一个tomcat服务器,只需要引入spring-boot-starter-web,在应用启动时会自动启动嵌入版的tomcat作为应用服务器,下面我们来分析这个过程。
之前我们分析了Spring boot的启动流程和自动配置流程,而嵌入tomcat的启动同样需要依赖一个配置ServletWebServerFactoryAutoConfiguration,我们来看一下这个配置的内容:
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(
this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
}
配置中主要做这么几件事,导入了内部类BeanPostProcessorsRegistrar,它实现了ImportBeanDefinitionRegistrar,作用我们在之前的文章中已经介绍过,我们可以实现ImportBeanDefinitionRegistrar来注册额外的BeanDefinition。导入了ServletWebServerFactoryConfiguration.EmbeddedTomcat配置(我们主要关注tomcat相关的配置)。注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer两个WebServerFactoryCustomizer类型的bean。我们来分别看一下上面提到的这些类的作用,首先来看内部类BeanPostProcessorsRegistrar:
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar:
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
这里注册了两个BeanDefinition,WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor,它们都实现了BeanPostProcessor,后面我们会介绍它们的作用。
ServletWebServerFactoryConfiguration.EmbeddedTomcat:
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
这个配置很简单,就是注册了一个TomcatServletWebServerFactory类型的bean。
最后剩下两个WebServerFactoryCustomizer类型的bean,任何WebServerFactoryCustomizer类型的bean都可以收到WebServerFactory的回调,所以我们可以实现WebServerFactoryCustomizer来定义WebServerFactory。ServletWebServerFactoryCustomizer的作用是将ServerProperties的属性配置设置到ConfigurableServletWebServerFactory的对应属性上,这里主要设置的是一些通用的属性,上面注册的TomcatServletWebServerFactory就实现了ConfigurableServletWebServerFactory。而TomcatServletWebServerFactoryCustomizer则是为TomcatServletWebServerFactory设置了一些它需要的特有属性。
在分析Spring boot启动流程时,我们看到Spring boot会根据是否能够成功加载一些关键的类来确认web应用类型,而我们通常的web应用类型为WebApplicationType.SERVLET,接下来就会根据这个类型来创建ApplicationContext(对Spring boot启动流程不了解的童鞋可以参考:Spring boot启动流程源码解析):
SpringApplication:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
// DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
所以在refresh上下文时调用的就是AnnotationConfigServletWebServerApplicationContext的refresh方法,在Spring源码解析之ApplicationContext这篇文章中我们分析了整个Spring上下文的初始化流程,主要分析的就是refresh方法,AnnotationConfigServletWebServerApplicationContext对refresh流程做了扩展,我们来看扩展实现:
ServletWebServerApplicationContext:
protected void onRefresh() {
super.onRefresh();
try {
/* 创建web服务 */
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
ServletWebServerApplicationContext:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 获取ServletWebServerFactory,这里就会获取到上文中注册bean TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
/* 获取ServletContextInitializer,获取WebServer */
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
// 替换Servlet相关的属性资源
initPropertySources();
}
ServletWebServerApplicationContext:
// ServletContextInitializer用于以编程方式配置Servlet 3.0+
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 使用给定的完全加载的ServletContext准备WebApplicationContext
prepareWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
// 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
// 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
TomcatServletWebServerFactory:
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 这里主要是一些embed tomcat的api调用
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);
}
// 准备TomcatEmbeddedContext并设置到tomcat中
prepareContext(tomcat.getHost(), initializers);
/* 构建TomcatWebServer */
return getTomcatWebServer(tomcat);
}
TomcatServletWebServerFactory:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
/* 初始化 */
initialize();
}
TomcatWebServer:
private void initialize() throws WebServerException {
TomcatWebServer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
// egnineName拼接instanceId
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
/* 删除Connectors,以便在启动服务时不发生协议绑定 */
removeServiceConnectors();
}
});
// 启动服务触发初始化侦听器
this.tomcat.start();
// 我们可以直接在主线程中重新抛出失败异常
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// 未启用命名,继续
}
// 与Jetty不同,所有Tomcat线程都是守护线程。我们创建一个阻塞非守护线程来避免立即关闭
startDaemonAwaitThread();
}
catch (Exception ex) {
// 异常停止tomcat
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
TomcatWebServer:
private void removeServiceConnectors() {
for (Service service : this.tomcat.getServer().findServices()) {
Connector[] connectors = service.findConnectors().clone();
// 将将要移除的connector放到缓存中暂存
this.serviceConnectors.put(service, connectors);
for (Connector connector : connectors) {
// 移除connector
service.removeConnector(connector);
}
}
}
这里涉及到一些embed tomcat的api调用,读者可以先去了解一下相关API。
下面我们再看另一个扩展refresh流程的方法:
ServletWebServerApplicationContext:
protected void finishRefresh() {
// 首先调用父类的finishRefresh方法,这个过程我们在上文提供的文章中分析过
super.finishRefresh();
/* 启动WebServer */
WebServer webServer = startWebServer();
if (webServer != null) {
// 发布WebServer初始化完成事件
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
ServletWebServerApplicationContext:
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
/* 启动WebServer */
webServer.start();
}
return webServer;
}
TomcatWebServer:
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
/* 添加之前移除的connector */
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
/* 延迟加载启动 */
performDeferredLoadOnStartup();
}
// 检查connector启动状态是否为失败,失败抛出异常
checkThatConnectorsHaveStarted();
this.started = true;
TomcatWebServer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true)
+ " with context path '" + getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
// 异常停止tomcat
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new WebServerException("Unable to start embedded Tomcat server",
ex);
}
finally {
Context context = findContext();
// context解绑ClassLoader
ContextBindings.unbindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
}
}
TomcatWebServer:
private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
// 从上面移除connector添加的缓存中取出connector
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
// 添加到tomcat service中
service.addConnector(connector);
if (!this.autoStart) {
// 如果不是自动启动,则暂停connector
stopProtocolHandler(connector);
}
}
// 添加完成后移除
this.serviceConnectors.remove(service);
}
}
}
TomcatWebServer:
private void performDeferredLoadOnStartup() {
try {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof TomcatEmbeddedContext) {
/* 延迟加载启动 */
((TomcatEmbeddedContext) child).deferredLoadOnStartup();
}
}
}
catch (Exception ex) {
TomcatWebServer.logger.error("Cannot start connector: ", ex);
throw new WebServerException("Unable to start embedded Tomcat connectors",
ex);
}
}
TomcatEmbeddedContext:
public void deferredLoadOnStartup() {
// 一些较旧的Servlet框架(例如Struts,BIRT)使用Thread上下文类加载器在此阶段创建servlet实例
// 如果他们这样做然后尝试初始化它们,那么类加载器可能已经改变了
// 所以将调用包装在loadOnStartup中我们认为它将成为运行时的主要webapp类加载器
ClassLoader classLoader = getLoader().getClassLoader();
ClassLoader existingLoader = null;
if (classLoader != null) {
existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader);
}
if (this.overrideLoadOnStart) {
// 早期版本的Tomcat使用了返回void的版本
// 如果使用该版本,则不会调用我们重写的loadOnStart方法,并且原来的方法已经运行
// 在Web应用程序部署描述符中加载并初始化标记为“load on startup”的所有servlet
super.loadOnStartup(findChildren());
}
if (existingLoader != null) {
ClassUtils.overrideThreadContextClassLoader(existingLoader);
}
}
最后我们来看在应用上下文关闭时调用的扩展:
ServletWebServerApplicationContext:
protected void onClose() {
super.onClose();
/* 停止并释放WebServer */
stopAndReleaseWebServer();
}
ServletWebServerApplicationContext:
private void stopAndReleaseWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
try {
/* 停止WebServer */
webServer.stop();
this.webServer = null;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
TomcatWebServer:
public void stop() throws WebServerException {
synchronized (this.monitor) {
boolean wasStarted = this.started;
try {
this.started = false;
try {
// 停止tomcat
stopTomcat();
// 销毁服务,调用后对象不能再次使用
this.tomcat.destroy();
}
catch (LifecycleException ex) {
}
}
catch (Exception ex) {
throw new WebServerException("Unable to stop embedded Tomcat", ex);
}
finally {
if (wasStarted) {
containerCounter.decrementAndGet();
}
}
}
}
到这里,整个Spring boot内嵌tomcat的流程就分析完了。