Skip to content

Latest commit

 

History

History
365 lines (278 loc) · 12 KB

springTomcat.md

File metadata and controls

365 lines (278 loc) · 12 KB

SpringBoot中如何启动Tomcat流程

本章代码基于springboot 2.2.2

SpringBoot项目之所以部署简单,其很大一部分原因就是因为不用自己折腾Tomcat相关配置,因为其本身内置了各种Servlet容器。 一直好奇: SpringBoot是怎么通过简单运行一个main函数,就能将容器启动起来,并将自身部署到其上 。此文想梳理清楚这个问题。

我们从SpringBoot的启动入口中分析:

Context 创建
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();

在SpringBoot 的 run 方法中,我们发现其中很重要的一步就是上面的一行代码。注释也写的很清楚:

创建、加载、刷新、运行 ApplicationContext。

继续往里面走。

/**
 * Strategy method used to create the {@link ApplicationContext}. By default this
 * method will respect any explicitly set application context or application context
 * class before falling back to a suitable default.
 * @return the application context (not yet refreshed)
 * @see #setApplicationContextClass(Class)
 */
protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_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);
}

逻辑很清楚:

先找到 context 类,然后利用工具方法将其实例化。

如果是 web 环境,则加载DEFAULT_SERVLET_WEB_CONTEXT_CLASS类。参看成员变量定义,其类名为:

AnnotationConfigServletWebServerApplicationContext

此类的继承结构如图:

直接继承GenericWebApplicationContext。关于该类前文已有介绍,只要记得它是专门为 web application提供context 的就好。

refresh

在经历过 Context 的创建以及Context的一系列初始化之后,调用 Context 的 refresh 方法,真正的好戏才开始上演。

SpringApplication

	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

可以看到refresh直接调用的试 AbstractApplicationContext 的refresh()方法

而且查看 AnnotationConfigServletWebServerApplicationContext 本身没有实现refresh(), 找到其直接父类:ServletWebServerApplicationContext也是调用上级方法到 AbstractApplicationContext

@Override
	public final void refresh() throws BeansException, IllegalStateException {
		try {
			super.refresh();
		}
		catch (RuntimeException ex) {
			stopAndReleaseWebServer();
			throw ex;
		}
	}

AbstractApplicationContext

	@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();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

看11行 onRefresh();, AbstractApplicationContext本身onRefresh()没有实现任何东西;最后我们回归到它的实现类。 根据上面的类结构图我们找到 ServletWebServerApplicationContext

ServletWebServerApplicationContext

    @Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
    }

我们重点看第3行。代码第3行createWebServer根据名字是要创建一个web服务,

	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			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();
	}

根据逻辑和进入getServletContext()可知,这里webServer和servletContext应该都是null或者servletContext不为null

  • 先看都是null的情况

获取到了一个ServletWebServerFactory,这是一个接口我们可以找到5个实现。不用想当然是要看TomcatServletWebServerFactory

ServletWebServerFactory的五个实现

TomcatServletWebServerFactory

	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		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 getTomcatWebServer(tomcat);
	}

哈哈哈!看见Tomcat了。

从第7行 Connector connector = new Connector(this.protocol) 一直到第16行完成了tomcat的connector的添加。 tomcat中的connector主要负责用来处理http请求,具体原理可以参看Tomcat的源码,此处暂且不提。

prepareContext方法有点长,重点看其中的几行:

		if (isRegisterDefaultServlet()) {
			addDefaultServlet(context);
		}
		if (shouldRegisterJspServlet()) {
			addJspServlet(context);
			addJasperInitializer(context);
		}
		context.addLifecycleListener(new StaticResourceConfigurer(context));
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		host.addChild(context);
		configureContext(context, initializersToUse);

前面两个分支判断添加了默认的servlet类和与jsp 相关的 servlet 类。

对所有的ServletContextInitializer进行合并后,利用合并后的初始化类对context 进行配置。

返回TomcatServletWebServerFactory,顺着getTomcatWebServer方法一直往下走,开始正式启动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();
					}
				});

				// Start the server to trigger initialization listeners
				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);
			}
		}
	}

this.tomcat.start();正式启动 tomcat。

现在我们回过来看看之前的那个 **getSelfInitializer()**方法:

是这里的 this.webServer = factory.getWebServer(getSelfInitializer());
	/**
	 * Returns the {@link ServletContextInitializer} that will be used to complete the
	 * setup of this {@link WebApplicationContext}.
	 * @return the self initializer
	 * @see #prepareWebApplicationContext(ServletContext)
	 */
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}

	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

prepareWebApplicationContext方法中主要是将ServletContext设置为rootContext。

registerApplicationScope允许用户存储自定义的scope。并且将web专用的scope注册到BeanFactory中,比如("request", "session", "globalSession", "application")。

WebApplicationContextUtils.registerEnvironmentBeans 注册web专用的environment bean(比如 ("contextParameters", "contextAttributes"))到给定的 BeanFactory 中。

beans.onStartup(servletContext)比较重要,主要用来配置 servlet、filters、listeners、context-param和一些初始化时的必要属性。

以其一个实现类ServletContextInitializer试举一例:

	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}