SpringBoot中内置的Tomcat如何启动的?

一、Tomcat相关配置类如何加载的?

在springboot项目中,我们只需要引入spring-boot-starter-web依赖,启动服务成功,我们一个web服务就搭建好了,没有明显的看到tomcat。其实打开spring-boot-starter-web依赖,我们可以看到:依赖了tomcat。

SpringBoot中内置的Tomcat如何启动的?_第1张图片

1.进入Springboot启动类。我们加入Springboot最核心的注解@SpringBootApplication,源码如下图:重点看注解@EnableAutoConfiguration,

SpringBoot中内置的Tomcat如何启动的?_第2张图片

 2.进入注解@EnableAutoConfiguration,如下图:该注解通过@Import注解导入了AutoConfigurationImportSelector类。其实这个类,就是导入通过加载配置文件,加载了很多工厂方法的配置类。

SpringBoot中内置的Tomcat如何启动的?_第3张图片

3.进入AutoConfigurationImportSelector类。首先调用selectImport()方法,在该方法中调用了 getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()方法, getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类。

详细讲解如下:也就是下图的,方法1调用方法2,方法2调用方法3:

SpringBoot中内置的Tomcat如何启动的?_第4张图片

到了这里加载了 META-INF/spring.factories文件:

SpringBoot中内置的Tomcat如何启动的?_第5张图片

 4.我们看到,加载了ServletWebServerFactoryAutoConfiguration这个配置类,web工厂配置类。

@Configuration(proxyBeanMethods = false)
@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 {
  ...
}

从这个配置工厂类,我们看出通过@Import注解加载了tomcat,jetty,undertow三个web服务器的配置类。由于没有导入jetty和undertow的相关jar包,这两个类实例的不会真正的加载。

5.进入EmbeddedTomcat类,创建了TomcatServletWebServerFactory类的对象。

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider connectorCustomizers,
				ObjectProvider contextCustomizers,
				ObjectProvider> protocolHandlerCustomizers) {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}
	}

6.进入TomcatServletWebServerFactory类,关注getWebServer()方法:

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		//实例化一个Tomcat
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		//设置Tomcat的工作临时目录
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		//默认使用Http11NioProtocal实例化Connector
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		//给Service添加Connector
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		//关闭热部署
		tomcat.getHost().setAutoDeploy(false);
		//配置Engine
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		// 实例化TomcatWebServer时会将DispatcherServlet以及一些Filter添加到Tomcat中
		return getTomcatWebServer(tomcat);
	}

getWebServer()方法在当前类,调用了getTomcatWebServer()方法,其实又new TomcatWebServer()对象:

	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

7.进入TomcatWebServer类,这个类才是真正的做tomcat启动的类:

(1)构造方法:调用了initialize()方法

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

(2)进入initialize()方法,这个方法:this.tomcat.start(),启动tomcat容器了

private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Tomcat在这里启动了
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

二、getWebServer()的调用分析,也就是tomcat何时启动的

上面分析了tomcat的配置到启动的方法,我们现在来分析,tomcat是何时启动的。

1.首先进入SpringBoot启动类的run方法:

	public static void main(String[] args) {
		SpringApplication.run(SpringBootMytestApplication.class, args);
	}

最终调用了本类的一个同名方法:

public ConfigurableApplicationContext run(String... args) {
		//记录程序运行时间
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// ConfigurableApplicationContext Spring 的上下文
		ConfigurableApplicationContext context = null;
		Collection exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		//【1、获取并启动监听器】
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//【2、构造应用上下文环境】
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//处理需要忽略的Bean
			configureIgnoreBeanInfo(environment);
			//打印banner
			Banner printedBanner = printBanner(environment);
			///【3、初始化应用上下文】
			context = createApplicationContext();
			//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//【4、刷新应用上下文前的准备阶段】
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//【5、刷新应用上下文】
			refreshContext(context);
			//【6、刷新应用上下文后的扩展接口】
			afterRefresh(context, applicationArguments);
			//时间记录停止
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//发布容器启动完成事件
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

这个方法大概做了以下几件事

1)获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作

2)构造容器环境,简而言之就是加载系统变量,环境变量,配置文件

3).创建容器

4) 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误 

5)准备容器

6) 刷新容器 

7)刷新容器后的扩展接口

2.那么内置tomcat启动源码,就是隐藏在上诉第六步:refreshContext方法里面,该方法最终会调 用到AbstractApplicationContext类的refresh()方法,进入refreshContext()方法,如图:

	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

 refreshContext()调用了refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

refresh()方法调用了this.onRefresh():

	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			//核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

 如下面的代码:createWebServer() 方法调用了一个factory.getWebServer()。

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			//先获取嵌入式Servlet容器工厂
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

到了这里getWebServer()方法,下一步就是创建TomcatWebServer对象,创建该对象,就在构造方法启动了Tomcat。详细代码在第一部分有。

总结:tomcat启动流程

SpringBoot中内置的Tomcat如何启动的?_第6张图片

你可能感兴趣的