缺省配置Springboot Web应用中tomcat的启动过程

文章目录

  • 概述
    • 独立部署的`tomcat`服务器的启动过程
    • `spring boot web`应用启动过程和独立部署的`tomcat`服务器启动过程的不同点
    • `spring boot web`应用启动过程概述
  • `TomcatEmbeddedServletContainerFactory`作为bean定义注册到容器
    • `EmbeddedServletContainerAutoConfiguration`
  • 内置`Tomcat servlet`容器的创建和初始化
    • `EmbeddedWebApplicationContext`的方法`createEmbeddedServletContainer`
    • TomcatEmbeddedServletContainerFactory的方法getEmbeddedServletContainer
    • TomcatEmbeddedServletContainer的创建和初始化
      • Tomcat.start()
    • Tomcat StandardContxt启动过程中SCI的应用
      • EmbeddedWebApplicationContext的selfInitialize
      • ServerProperties内部类SessionConfiguringInitializer
      • InitParameterConfiguringServletContextInitializer
  • 内置`Tomcat servlet`容器的启动
    • 启动入口点
    • 启动入口点代码分析
    • 内置servlet容器的启动过程
    • Tomcat Connector的启动
      • 启动入口点
      • 启动入口点逻辑分析
      • Connector启动过程分析
      • Http11NioProtocol启动过程分析
  • 参考文章

该分析基于 Springboot 1.5.8 RELEASE

概述

独立部署的tomcat服务器的启动过程

传统意义上一个独立部署和运行的tomcat服务器的启动可以理解成两个阶段 :

  1. tomcat 容器本身的启动;

  2. tomcat容器中所部署的web app的启动;

完成了以上两个阶段,我们才能访问到我们所开发的业务逻辑。在这种情况下,web app的部署动作,通常是由系统部署人员通过某种方式在启动服务器前完成的。

spring boot web应用启动过程和独立部署的tomcat服务器启动过程的不同点

相对于一个独立部署和运行的tomcat服务器,一个缺省配置的spring boot web应用,情况有些不同 :

  1. tomcat 不再是独立存在的,是被内嵌到应用中的;

  2. web app的部署不是系统部署人员部署的,是spring boot 应用按照某种约定运行时组装和部署的;

在启动spring boot web应用之前,开发人员需要按照 spring boot的规范编码,实现特定的类,打包部署该spring boot web应用,然后才能启动该应用并提供相应的服务。

本文中介绍的 spring boot web应用的启动过程,指的是从输入spring boot web应用的启动命令开始到开发人员所实现web app的启动完成。

spring boot web应用启动过程概述

前提 :

  1. 缺省情况下web应用引用了依赖 spring-boot-starter-tomcat

  2. TomcatEmbeddedServletContainerFactoryspring-boot-autoconfigure创建内置tomcat servlet容器的工厂类;

启动过程 :

  1. 在自动配置工具spring-boot-autoconfigure执行的自动配置阶段,EmbeddedServletContainerAutoConfiguration会因为spring-boot-starter-tomcat的存在而注册bean定义 TomcatEmbeddedServletContainerFactory ;

  2. 然后在spring boot启动web容器的阶段,应用上下文 application context 会使用注册的该bean定义 TomcatEmbeddedServletContainerFactory 创建并启动一个内置的 tomcat servlet 容器
    TomcatEmbeddedServletContainer

该过程中spring boot会将以bean定义形式注册到bean容器的的Servlet,Filter,Event Listener,Web Controllerweb app元素关联到tomcat形成一个web app,然后对外提供服务

TomcatEmbeddedServletContainerFactory作为bean定义注册到容器

EmbeddedServletContainerAutoConfiguration

在spring boot自动配置工具spring-boot-autoconfigure的元数据文件META-INF/spring.factories
定义自动配置属性org.springframework.boot.autoconfigure.EnableAutoConfiguration时,该属性
的值包含了如下值 :

org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration

EmbeddedServletContainerAutoConfiguration 的实现(部分):

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration
 	// 这里 Servlet, Tomcat 是spring-boot-starter-tomcat提供的,
	// 当 spring-boot-starter-tomcat 被引入到 classpath 时,以下@ConditionalOnClass
	// 的条件会被满足,从而应用该配置类,注册bean定义 TomcatEmbeddedServletContainerFactory,
	// 也就是整个web应用启动web servlet容器时所要使用的servlet容器工厂类
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, 
		search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory()
		 {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}
//...
}

内置Tomcat servlet容器的创建和初始化

调用入口点 :

// 由此调用链可以看出,内置Tomcat servlet容器的创建和初始化实在Spring ApplicationContext
// 容器的refresh()过程中执行的。
SpringApplication.run()
=>refresh(ApplicationContext applicationContext)
	=>EmbeddedWebApplicationContext.refresh()
		=> super.refresh()
	=>EmbeddedWebApplicationContext.onRefresh()
	=>createEmbeddedServletContainer()

EmbeddedWebApplicationContext的方法createEmbeddedServletContainer

	private void createEmbeddedServletContainer() {
		// 获取属性中记录的嵌入式servlet容器,spring boot web 应用启动时这里一定是 null
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		// 获取属性中记录的嵌入式servlet上下文,spring boot web 应用启动时这里一定是 null
		ServletContext localServletContext = getServletContext();
		if (localContainer == null && localServletContext == null) {
			// spring boot web 应用启动时流程会走到这里
			// 获取ApplicationContext bean定义注册阶段注册的EmbeddedServletContainerFactory
			// 对于使用缺省配置的spring boot web 应用,这里实际上是 
			// TomcatEmbeddedServletContainerFactory
			// 该bean实例化过程中已经通过BeanPostProcessor应用了各种
			// EmbeddedServletContainerCustomizer,
			// 比如 bean ServerProperties,DuplicateServerPropertiesDetector 等
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			// 创建相应的嵌入式 servlet 容器并完成配置和初始化
			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();
	}

TomcatEmbeddedServletContainerFactory的方法getEmbeddedServletContainer

	// 所在包 : package org.springframework.boot.context.embedded.tomcat;
	@Override
	public EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers) {
		// 创建内置tomcat servlet 容器的启动器			
		// tomcat有多种配置和启动方式,最常见的方式是基于server.xml配置的启动器 
		// org.apache.catalina.startup.Bootstrap, 而这里的Tomcat是一个内嵌tomcat的启动器
		// 这个Tomcat启动器缺省会 : 
		// 1. 创建一个Tomcat Server,
		// 2. 创建并关联到这个 Tomcat Server上一个 Tomcat Service: 
		//     1 Tomcat Service = 1 Tomcat Engine + N Tomcat Connector
		// 每个Service只能包含一个Servlet引擎(Engine), 表示一个特定Service的请求处理流水线。
		// 做为一个Service可以有多个连接器,引擎从连接器接收和处理所有的请求,将响应返回给
		// 适合的连接器,通过连接器传输给用户。
		// 用户可以通过实现Engine接口提供自定义引擎,但通常不需要这么做。
		// 3. 在所创建的那一个 Tomcat Engine 上创建了一个 Tomcat Host 。
		// 每个 Virtual Host 虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个
		// 或者多个Web App,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,
		// 将把该请求匹配到某个Context上。 一个 Tomcat Engine 上面可以有多个 Tomcat Host。
		Tomcat tomcat = new Tomcat();
		// 如果设置了baseDirectory则使用之,否则,在文件系统当前用户的临时目录下创建基础工作目录
		// 缺省情况下,baseDirectory 是没有被设置的,新建的临时目录类似于 :
		// C:\Users\ZHANGSAN\AppData\Local\Temp\tomcat.6659819252528658851.8080
		File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat"));
		// 设置tomcat的基础工作目录				
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		// 创建tomcat的Connector,缺省协议为org.apache.coyote.http11.Http11NioProtocol,
		// 表示处理 http v1.1 协议
		Connector connector = new Connector(this.protocol);
		// 这里 getService()方法内部会调用 getServer()方法,该方法真正创建 Tomcat 的 Server 对象实例,
		// 其实现类是 org.apache.catalina.core.StandardServer
		tomcat.getService().addConnector(connector);
		// 根据配置参数定制 connector :端口,uri encoding字符集,是否启用SSL, 是否使用压缩等
		// 缺省情况下端口是 8080, uri encoding 是 utf-8
		customizeConnector(connector);
		tomcat.setConnector(connector);
		// 关闭应用的自动部署
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());

		// 如果指定了更多附加的Tomcat Connector,也添加进来
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		
		// 准备Tomcat StandardContext,对应一个webapp,并将其通过host关联到tomcat
		// 参考下面方法prepareContext的注释
		prepareContext(tomcat.getHost(), initializers);

		// 创建 TomcatEmbeddedServletContainer 并初始化
		// 其中包括调用 tomcat.start()
		return getTomcatEmbeddedServletContainer(tomcat);
	}

	/**
	  * 纯程序方式创建并准备Tomcat StandardContext,它对应一个web应用,把它绑定到host上。
	  * 参数initializers是上面步骤提供的SCI,将它关联到Tomcat StandardContext。
	  **/
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
		// 准备Host的docBase,
		File docBase = getValidDocumentRoot();
		// spring boot web应用启动过程中上面获得docBase会为null,实际会采用下面的
		// 创建临时目录动作完成 docBase的创建
		// 新建的 docBase的目录类似于 :
		// C:\Users\ZHANGSAN\AppData\Local\Temp\tomcat-docbase.5929365937314033823.8080
		// !!! 注意这里的 docBase 和上面提到的 baseDir 不同
		docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));

		// 创建StandardContext,这是Tomcat的标准概念,用来对应表示一个web应用,
		// 这里使用实现类TomcatEmbeddedContext,由 spring boot 提供。
		// 以下创建和初始化一个TomcatEmbeddedContext的过程,可以认为是往tomcat servlet
		// 容器中部署和启动一个web应用的过程,只不过在传统方式下,一个web应用部署到tomcat使用
		// war包的方式,而这里是完全程序化的方式。
		final TomcatEmbeddedContext context = new TomcatEmbeddedContext();
		// 设置StandardContext的名字,使用 context path,这个路径以/开始,没有/结尾;如果是根
		// StandardContext,这个 context path 为空字符串(0长度字符串);
		context.setName(getContextPath());
		context.setDisplayName(getDisplayName());
		context.setPath(getContextPath());
		context.setDocBase(docBase.getAbsolutePath());
		context.addLifecycleListener(new FixContextListener());
		context.setParentClassLoader(
				this.resourceLoader != null ? this.resourceLoader.getClassLoader()
						: ClassUtils.getDefaultClassLoader());
		resetDefaultLocaleMapping(context);
		addLocaleMappings(context);
		try {
			context.setUseRelativeRedirects(false);
		}
		catch (NoSuchMethodError ex) {
			// Tomcat is < 8.0.30. Continue
		}
		SkipPatternJarScanner.apply(context, this.tldSkipPatterns);
		WebappLoader loader = new WebappLoader(context.getParentClassLoader());
		loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
		loader.setDelegate(true);
		context.setLoader(loader);
		if (isRegisterDefaultServlet()) {
			// 缺省情况下,会注册 Tomcat 的 DefaultServlet,
			// DefaultServlet是Tomcat缺省的资源服务Servlet,用来服务HTML,图片等静态资源
			// 配置信息 :
			// servletClass : org.apache.catalina.servlets.DefaultServlet
			// name : default
			// overridable : true
			// initParameter : debug, 0
			// initParameter : listings, false
			// loadOnStartup : 1
			// servletMapping : / , default
			addDefaultServlet(context);
		}
		if (shouldRegisterJspServlet()) {
			// Spring boot 提供了一个工具类 org.springframework.boot.context.embedded.JspServlet
			// 检测类 org.apache.jasper.servlet.JspServlet 是否存在于 classpath 中,如果存在,
			// 则认为应该注册JSP Servlet。
			// 缺省情况下,不注册(换句话讲,Springboot web应用缺省不支持JSP)
			// 注意 !!! 这一点和使用Tomcat充当外部容器的情况是不一样的,                                  
			// 使用Tomcat作为外部容器的时候,JSP Servlet 缺省是被注册的。
			// 如果想在 Spring boot中支持JSP,则需要将 tomcat-embed-jasper 包加入 classpath 中。
			// 配置信息 :
			// servletClass : org.apache.jasper.servlet.JspServlet
			// name : jsp
			// initParameter : fork, false
			// initParameter : development, false
			// loadOnStartup : 3
			// servletMapping : *.jsp , jsp
			// servletMapping : *.jspx , jsp
			addJspServlet(context);
			// Jasper 把JSP文件解析成java文件,然后编译成JVM可以使用的class文件。
			// 有很多的JSP解析引擎,Tomcat中使用的是Jasper。
			// 参考资料 : http://tomcat.apache.org/tomcat-8.0-doc/jasper-howto.html
			// 下面添加的 Jasper initializer 用于初始化 jasper。
			addJasperInitializer(context);
			context.addLifecycleListener(new StoreMergedWebXmlListener());
		}
		context.addLifecycleListener(new LifecycleListener() {

			@Override
			public void lifecycleEvent(LifecycleEvent event) {
				if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
					TomcatResources.get(context)
							.addResourceJars(getUrlsOfJarsWithMetaInfResources());
				}
			}

		});
		// 合并参数提供的Spring SCI : EmbeddedWebApplicationContext$1,
		// 这是一个匿名内部类,封装的逻辑来自方法 selfInitialize()
		// 和当前servlet容器在bean创建时通过EmbeddedServletContainerCustomizer
		// ServerProperties添加进来的两个Spring SCI :
		// ServerProperties$SessionConfiguringInitializer
		// InitParameterConfiguringServletContextInitializer
		// 注意这里的SCI接口由spring定义,tomcat jar中也包含了一个servlet API规范
		// 定义的SCI接口,这是定义相同的两个接口而非同一个,最终实现了Spring SCI接口的
		// 类的逻辑必须通过某种方式封装成实现了servlet API规范定义的SCI的逻辑才能被
		// 执行
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		// 配置context,
		// 1.将Spring提供的SCI封装成Servlet API标准SCI配置到context中去,
		// 通过一个实现了Servlet API标准SCI接口的spring类 TomcatStarter
		// 2.将spring领域的MIME映射配置设置到context中去,
		// 3.将spring领域的session配置设置到context中去,比如 sessionTimeout
		configureContext(context, initializersToUse);
		// 将该context关联到host上去
		host.addChild(context);
		// 内部实现为空
		postProcessContext(context);
	}

	// 创建TomcatEmbeddedServletContainer并初始化
	protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
			Tomcat tomcat) {
		// 第二个参数要填充目标实例的属性autoStart,
		缺省端口为8080,所以getPort() >=0true,也就是传递 autoStart为true
		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
	}	

TomcatEmbeddedServletContainer的创建和初始化

	// TomcatEmbeddedServletContainer有一个成员变量started,初始化值为 false,
	// 用来表示容器是否处于已经启动状态
	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;//设置是否要自动启动标志
		// 初始化当前TomcatEmbeddedServletContainer,
		// 主要是调用 tomcat.start(),该初始化过程会初始化和启动tomcat的server,service,engine,
		// 会初始化相应的 connector, 但是并不会启动该 connector, 而是将其删除,在随后该
		// TomcatEmbeddedServletContainer 的 start() 启动阶段,再将该 connector 添加到
		// tomcat 的 service 中,然后启动该 connector。
		initialize(); 
	}

	private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				// 往引擎名字中增加instance Id信息,如果 instance id为0,则不修改引擎名字
				addInstanceIdToEngineName();
				try {
					// Remove service connectors to that protocol binding doesn't happen
					// yet
					// 注意,从容器中把Connector删掉,这样下面随后马上执行的start()动作
					// 只会启动容器中除了Connector之外的其他部分
					// 注意,在更新版本的embedded Tomcat中,这里的逻辑变化了,变成了
					// 在 service 启动后 protocal binding 尚未发生之前执行删除 service 中
					// connector 的逻辑。
					removeServiceConnectors();

					// Start the server to trigger initialization listeners
					// 1.触发启动Tomcat容器中除了Connector之外的其他部分,
					// Connector此处没被启动意味着该启动过程完成后,服务器还是不能接受来自
					// 网络的请求,因为Connector才是真正负责接受网络请求的入口。		
					// 2. 这里Tomcat启动的主要是
					// StandardServer[1实例,Tomcat Lifecycle] =>
					// StandardService[1实例,Tomcat Lifecycle] =>
				    // StandardEngine[1实例,Tomcat Container] =异步startStopExecutor =>
				    // StandardHost[1实例,Tomcat Container] =异步startStopExecutor =>
				    // TomcatEmbeddedContext[1实例,Springboot实现的Tomcat Container] =>
					// StandardWrapper[1实例,Tomcat Container]。
					// 这里StandardWrapper对应的Servlet是Spring MVC的DispatchServlet。
					// 上面Tomcat Container父容器启动子容器都是通过线程池异步方式启动的。
					this.tomcat.start();

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

					Context context = findContext();
					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								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
					// tomcat 自身所有的线程都是daemon线程。这里spring创建了一个非daemon线程用来
					// 阻塞整个应用,避免刚启动就马上结束的情况。
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}

Tomcat.start()

  /**
     * Start the server.
     *
     * @throws LifecycleException Start error
     */
    public void start() throws LifecycleException {
	    //创建 tomcat StandardServer, 初始化基础目录, 
	    // 创建 tomcat StandardService并关联到 tomcat StandardServer。
        getServer();
        // 获取 tomcat StandardService上是否已经绑定Connector,如果没有,
        // 创建一个支持 HTTP/1.1的Connector,设置到执行端口,然后将该Connector     
        // 绑定到 tomcat StandardService。
        getConnector();

		// 启动  tomcat StandardServer。这里会对相应的 server,service,engine,
		// 分别进行初始化和启动,也就是调用他们的init()方法和 start()方法。
		// 而 connector只执行了初始化 init(),然后被从 service 中删掉。
		// 该被删除的 connector 随后会被添加回来,然后再调用其启动start()方法。
        server.start();
    }
 /**
     * Get the server object. You can add listeners and few more
     * customizations. JNDI is disabled by default.
     * @return The Server
     */
    public Server getServer() {

        if (server != null) {
            return server;
        }

        System.setProperty("catalina.useNaming", "false");

        server = new StandardServer();

        initBaseDir();

        server.setPort( -1 );

        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }    
        /**
     * Get the default http connector. You can set more
     * parameters - the port is already initialized.
     *
     * 

* Alternatively, you can construct a Connector and set any params, * then call addConnector(Connector) * * @return A connector object that can be customized */ public Connector getConnector() { Service service = getService(); if (service.findConnectors().length > 0) { return service.findConnectors()[0]; } if (defaultConnectorCreated) { return null; } // The same as in standard Tomcat configuration. // This creates an APR HTTP connector if AprLifecycleListener has been // configured (created) and Tomcat Native library is available. // Otherwise it creates a NIO HTTP connector. Connector connector = new Connector("HTTP/1.1"); connector.setPort(port); service.addConnector(connector); defaultConnectorCreated = true; return connector; }

Tomcat StandardContxt启动过程中SCI的应用

上面过程创建的StandardContext实例,也就是所对应的webapp,已经关联但是尚未应用spring提供的SCI。这些SCI的应用是在StandardContext.startInternal()中应用SCI阶段进行的。

  // 调用 ServletContainerInitializer(备注 : 缩写为SCI)
  // 这里的SCI接口是Servlet API标准SCI接口,而不是Spring定义的SCI接口。上面的流程分析中已经讲到,
  // prepareContext()过程中已经将Spring准备的多个Spring SCI封装成一个Servlet API规范SCI
  // 实现TomcatStarter关联到了tomcat容器,这里的initializers 就是这些封装后的Servlet API
  //  SCI实例。在使用缺省配置的Springboot Web应用中,以下for循环中的initializers其实只有一个,
  // 就是前面提到的Spring提供的实现了Servlet API标准SCI接口TomcatStarter。
  for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : 
      initializers.entrySet()) {
      try {
	      // 调用SCI的onStartup()方法,getServletContext()是基于当前StandardContext创建
	      // 的ServletContext的facade
          entry.getKey().onStartup(entry.getValue(),
                  getServletContext());
      } catch (ServletException e) {
          log.error(sm.getString("standardContext.sciFail"), e);
          ok = false;
          break;
      }
  }

这里对Tomcat标准SCI TomcatStarter 的调用,最终还是调用了Spring提供的3个SCI :

  1. EmbeddedWebApplicationContext匿名内部类,封装的逻辑来自方法 selfInitialize()
  2. ServerProperties$SessionConfiguringInitializer
  3. InitParameterConfiguringServletContextInitializer

EmbeddedWebApplicationContext的selfInitialize

	/**
	* servletContext是一个Java Servlet规范里面的概念,这里其实现由Tomcat提供
	* 
	**/
	private void selfInitialize(ServletContext servletContext) throws ServletException {
		// 1.将当前spring application context作为属性设置到servletContext :
		// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
		// 随后应用启动完在提供Web服务的时候,Spring MVC dispatchServlet的应用上下文
		// 的双亲就会使用这个根Web应用上下文		
		// 2.将servletContext记录到当前web application context,也就是当前
		// EmbeddedWebApplicationContext对象的属性servletContext中
		prepareEmbeddedWebApplicationContext(servletContext);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		// 获取用户自定义webapp作用域
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		//注册标准webapp作用域				
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		// 重新注册用户自定义webapp作用域				
		existingScopes.restore();
		// 注册webapp相关环境参数bean : contextParameters,contextAttributes
		// WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME
		// WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME
		// WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		// !!! 到目前为止,尚未初始化webapp中的Servlet,Filter 和 EventListener				
		// 从Bean容器中找到所有的SCI bean,并调用其 onStartup()回调方法
		// getServletContextInitializerBeans()会从bean容器中找到所有的SCI bean,
		// 将bean容器中所有Servlet, Filter 和EventListener bean转换成SCI 然后返回
		// 给该for循环然后逐一调用其 onStartup() 回调。
		// !!! 这里是真正的Servlet, Filter 和EventListener注入到ServletContext的触发点
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
		// 到此为止,一个完整的符合开发者设计目的的WebApp才算是被启动和被完整设置
	}
	/**
	 * Returns ServletContextInitializers that should be used with the embedded
	 * Servlet context. By default this method will first attempt to find
	 * ServletContextInitializer, Servlet, Filter and certain
	 * EventListener beans.
	 * 
	 * 返回所有需要被应用到Servlet context上的ServletContextInitializer。
	 * 
	 * 缺省情况下:
	 * 1.该方法会首先尝试bean容器中注册的 ServletContextInitializer bean,
	 * 2.然后是各种适配bean,比如 Servlet, Filter 和EventListener bean,
	 * 将它们封装成实现了接口 ServletContextInitializer 的 RegistrationBean 。
	 * 3.最后将上面所有找到的 ServletContextInitializer bean 和封装的 RegistrationBean 
	 * 返回给调用者。
	 * 
	 * 
	 * @return the servlet initializer beans
	 */
	protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
		return new ServletContextInitializerBeans(getBeanFactory());
	}	
	/**
	 * Prepare the WebApplicationContext with the given fully loaded
	 * ServletContext. This method is usually called from
	 * ServletContextInitializer#onStartup(ServletContext) and is similar to the
	 * functionality usually provided by a ContextLoaderListener.
	 * @param servletContext the operational servlet context
	 */
	protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) {
		// 从Servlet Context上面读取属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,根Web应用上下文
		Object rootContext = servletContext.getAttribute(
				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
		if (rootContext != null) {
		// 如果在 Servlet Context上面已经设置了属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
		// 这块逻辑表明根Web应用上下文只能初始化一次并绑定到Servlet Context的这个属性上				
			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 {
			// 将this对象,也就是当前EmbeddedWebApplicationContext Web应用上下文对象设置为
			// 当前ServletContext上下文的属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
			// 随后应用启动完在提供Web服务的时候,Spring MVC dispatchServlet的应用上下文
			// 的双亲就会使用这个根Web应用上下文
			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;
		}
	}
	

缺省spring boot web应用中ServletContextInitializerBeansbeanFactory所获得的SCI有 :

Bean 类型
dispatcherServlet ServletRegistrationBean
characterEncodingFilter FilterRegistrationBean
hiddenHttpMethodFilter FilterRegistrationBean
httpPutFormContentFilter FilterRegistrationBean
requestContextFilter FilterRegistrationBean

想了解以上各个bean都是什么用途,请参考 :
缺省配置Springboot Web应用启动过程中Bean定义的登记

ServerProperties内部类SessionConfiguringInitializer

设置SessionCookieConfig

InitParameterConfiguringServletContextInitializer

init参数设置到ServletContext上的一个SCI

内置Tomcat servlet容器的启动

在整个上面的启动过程中,虽然调用 Tomcat 实例的启动方法,但是整个容器tomcatEmbeddedServletContainer只能算是启动了一部分,也就是其中除了Tomcat Connector 之外的其他部分。

此时tomcatEmbeddedServletContainer实例的属性 started仍然为false,表示整个容器处于尚未启动的状态。所以上的逻辑更像是容器创建和初始化的过程。

Tomcat Connector是用来接收来自网络的请求的关键组件。这一部分组件启动之后,整个容器tomcatEmbeddedServletContainer才能接受和处理来自网络的请求从而为用户提供服务,所以所有这些的启动都结束,才能算是整个容器的启动完成,此时started属性才能设置为true

该小节容器的启动,主要就是讲容器中Tomcat Connector的启动。

启动入口点

SpringApplication.run()
=>refresh(ApplicationContext applicationContext)
	=>EmbeddedWebApplicationContext.refresh()
	=>EmbeddedWebApplicationContext.finishRefresh()
	=>startEmbeddedServletContainer()
	

启动入口点代码分析

	// 类EmbeddedWebApplicationContext的方法finishRefresh()
	@Override
	protected void finishRefresh() {
		//先调用父类AbstractApplicationContext的finishRefresh()
		super.finishRefresh();
		// 启动内置Servlet容器
		EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
		if (localContainer != null) {
			// 容器已经启动,可以接受来自外部的HTTP请求了,发布事件 :
			// EmbeddedServletContainerInitializedEvent
			publishEvent(
					new EmbeddedServletContainerInitializedEvent(this, localContainer));
		}
	}
	// 类EmbeddedWebApplicationContext的方法startEmbeddedServletContainer()
	private EmbeddedServletContainer startEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		if (localContainer != null) {
			// 调用内置servlet容器TomcatEmbeddedServletContainer实例上的start()方法
			localContainer.start();
		}
		return localContainer;
	}

内置servlet容器的启动过程

	// TomcatEmbeddedServletContainer 的start方法
	@Override
	public void start() throws EmbeddedServletContainerException {
		synchronized (this.monitor) {
			if (this.started) {
				return;
			}
			try {
				// 将内置容器创建和初始化阶段删除的Connector再添加到容器,将Connector添加回容器(实际上是添加到容器的Service),
				// 因为相应的Service已经处于启动状态,所以Connector在添加回来之后马上会被启动
				addPreviouslyRemovedConnectors();
				// 获得tomcat的Connector,如果不为空并且设置为自动启动,则启动之。缺省配置下,
				// 这里 autoStart 为 true
				// 连接器 Connector 主要是接收用户的请求,然后封装请求传递给容器处理,tomcat中默认的连接器是Coyote。
				// 连接器表示Tomcat将会在哪个端口使用哪种协议提供服务。
				// 在配置文件中,我们经常会见到这样的配置 : 
				// 
				//                redirectPort="8443" URIEncoding="utf-8"/> 
				// 
				Connector connector = this.tomcat.getConnector();
				if (connector != null && this.autoStart) {
					startConnector(connector);
				}
				// 检查确保Connector已经启动,如果没启动,抛出异常
				checkThatConnectorsHaveStarted();
				this.started = true;
				TomcatEmbeddedServletContainer.logger
						.info("Tomcat started on port(s): " + getPortsDescription(true));
			}
			catch (ConnectorStartFailedException ex) {
				stopSilently();
				throw ex;
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat servlet container", ex);
			}
			finally {
				Context context = findContext();
				ContextBindings.unbindClassLoader(context, getNamingToken(context),
						getClass().getClassLoader());
			}
		}
	}

	// TomcatEmbeddedServletContainer 的 addPreviouslyRemovedConnectors 方法
	private void addPreviouslyRemovedConnectors() {
		Service[] services = this.tomcat.getServer().findServices();
		for (Service service : services) {
			Connector[] connectors = this.serviceConnectors.get(service);
			if (connectors != null) {
				for (Connector connector : connectors) {
					// 此时service已经处于启动状态,因此重新添加进来的connector
					// 也没马上被执行启动动作
					service.addConnector(connector);
					if (!this.autoStart) {
						stopProtocolHandler(connector);
					}
				}
				this.serviceConnectors.remove(service);
			}
		}
	}

Tomcat Connector的启动

启动入口点

Service.addConnector()
  => Connector.start()

启动入口点逻辑分析

	//StandardService 的方法 addConnector
	//StandardService 是tomcat提供的类
    @Override
    public void addConnector(Connector connector) {

        synchronized (connectorsLock) {
	        // 将connector关联到当前 service
            connector.setService(this);
            Connector results[] = new Connector[connectors.length + 1];
            System.arraycopy(connectors, 0, results, 0, connectors.length);
            results[connectors.length] = connector;
            connectors = results;
			
			// 如果当前服务的状态是 available,则在将 connector 增加到service时
			// 直接启动 connector
            if (getState().isAvailable()) {
                try {
	                // 启动新增进来的 connector
                    connector.start();
                } catch (LifecycleException e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }

            // Report this property change to interested listeners
            support.firePropertyChange("connector", null, connector);
        }

    }

Connector启动过程分析

	// Connector是tomcat提供的类
    @Override
    protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        if (getPort() < 0) {
            throw new LifecycleException(sm.getString(
                    "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
        }

        setState(LifecycleState.STARTING);

        try {
	        // Connector启动的核心动作是协议处理器的启动
	        // 对于缺省配置的springboot web应用,它会在8080端口提供 HTTP 服务,
	        // 所以这里是一个处理http协议请求的 Http11NioProtocol 实例,使用 nio 方式处理 http 协议,
	        // Connector 对HTTP请求的接收和处理并不是亲自完成的,而是交给该 Http11NioProtocol  
	        // protocolHandler 完成,而 protocolHandler 又进一步将请求处理工作交给 NioEndpoint 完成。
            protocolHandler.start();
        } catch (Exception e) {
            String errPrefix = "";
            if(this.service != null) {
                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
            }

            throw new LifecycleException
                (errPrefix + " " + sm.getString
                 ("coyoteConnector.protocolHandlerStartFailed"), e);
        }
    }

Http11NioProtocol启动过程分析

	// 调用链 :
	// Connector.start()
	//  => startInternal()
	//    => Http11NioProtocol protocolHandler.start();
	// 
	// Http11NioProtocol 的 start方法,由基类 AbstractProtocol 提供实现
	// Http11NioProtocol ,AbstractProtocol 都是tomcat提供的类
    @Override
    public void start() throws Exception {
        if (getLog().isInfoEnabled())
            getLog().info(sm.getString("abstractProtocolHandler.start",
                    getName()));
        try {
	        // 启动了成员变量endpoint,一个 NioEndpoint 实例
	        // Http11NioProtocol 类实例自身并不最终处理请求,具体这些请求的处理
	        // 都是交给了 NioEndpint endpoint 来完成,这里启动该 endpoint。 
            endpoint.start();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.startError",
                    getName()), ex);
            throw ex;
        }

        // Start async timeout thread
        asyncTimeout = new AsyncTimeout();
        Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
        int priority = endpoint.getThreadPriority();
        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            priority = Thread.NORM_PRIORITY;
        }
        timeoutThread.setPriority(priority);
        timeoutThread.setDaemon(true);
        timeoutThread.start();
    }
	// 调用链 :
	// Connector.start()
	//  => startInternal()
	//    => Http11NioProtocol protocolHandler.start();
	//       => NioEndpoint endpoint.start()
	// 
	// NioEndpoint的start()方法,在其基类AbstractEndpoint中实现
	// NioEndpoint,AbstractEndpoint都是tomcat提供的类
    public final void start() throws Exception {
        if (bindState == BindState.UNBOUND) {
	        // 如果端口绑定状态为未绑定,这里执行端口绑定逻辑
            bind();
            bindState = BindState.BOUND_ON_START;
        }
        startInternal();
    }
    //AbstractEndpoint的bind()方法实现
    @Override
    public void bind() throws Exception {
		// 建立服务套接字,并绑定到指定的端口,
		// 缺省配置的spring-boot web应用,这里的端口是 8080
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        serverSock.socket().bind(addr,getAcceptCount());
        // 设置 serverSock  为阻塞模式
        serverSock.configureBlocking(true); //mimic APR behavior

        // Initialize thread count defaults for acceptor, poller
        // 初始化 acceptoer thread 的数量,默认为1
        if (acceptorThreadCount == 0) {
            // FIXME: Doesn't seem to work that well with multiple accept threads
            acceptorThreadCount = 1;
        }
        // 设置 pooler thread 的个数 pollerThreadCount
        // 注意变量 pollerThreadCount 在定义时已经默认初始化为2和CPU核数量二者的最小值,
        // 单核CPU的话pollerThreadCount默认初始值为1,多核CPU的话pollerThreadCount默认初始值总是为2
		//  private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
		// 这里只是做一下检查,确保其最少为1
        if (pollerThreadCount <= 0) {
            //minimum one poller thread
            pollerThreadCount = 1;
        }
        setStopLatch(new CountDownLatch(pollerThreadCount));

        // Initialize SSL if needed
        // 如果配置了SSL的话,对其进行初始化
        initialiseSsl();

        selectorPool.open();
    }   
     //AbstractEndpoint的startInternal()方法实现 
     /**
     * Start the NIO endpoint, creating acceptor, poller threads.
     */
    @Override
    public void startInternal() throws Exception {

        if (!running) {
	        // 设置运行状态
            running = true;
            paused = false;

            processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getProcessorCache());
            eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                            socketProperties.getEventCache());
			// NioChannel 实现了 NIO ByteChannel 接口
            nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                    socketProperties.getBufferPool());

            // Create worker collection
            // 创建工作线程池,Tomcat自己封装了一个 ThreadPoolExecutor
            if ( getExecutor() == null ) {
	            // 如果尚未指定 executor, 则创建内部的任务执行线程
	            // 缺省配置的 spring boot web 应用没有外部指定的 executor
	            // 创建的线程名称前缀为 http-nio-8080-exec-
                createExecutor();
            }
			
			// 创建一个LimitLatch,用于控制最大连接数,缺省值10000
            initializeConnectionLatch();

            // 创建和启动 Poller 线程
            // 1. Poller类是一个Runnable.每个Poller对象会保持一个Java NIO Selector
            //    和一个PollerEvent队列SynchronizedQueue.    
            // 2. Poller 线程数量会使用当前计算机CPU processor数量和2的最小值,
            //     多核CPU的话 pollerThreadCount默认为2,单核CPU的话默认为1
            // 3. 每个Poller线程都是 daemon 线程            
            // 4. Poller线程名称前缀 : http-nio-8080-ClientPoller-        
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }

			// 创建和启动请求接收线程
			// 1. 接收线程缺省只有1个
			// 2. 接收线程名称前缀 : http-nio-8080-Acceptor-
            startAcceptorThreads();
        }
    }
    //AbstractEndpoint的createExecutor()方法实现 
    public void createExecutor() {
        internalExecutor = true;
        // 创建基于ThreadPoolExecutor的任务执行队列
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        // 创建的线程名称前缀为 http-nio-8080-exec-
        // corePoolSize:10
        // maximunPoolSize:200
        // keepAliveTime : 60s
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }    

	 //AbstractEndpoint的startAcceptorThreads()方法实现 
    protected final void startAcceptorThreads() {
        int count = getAcceptorThreadCount(); // 上面已经分析过,这里的数量初始化为1
        acceptors = new Acceptor[count];

        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            // 线程名称 : http-nio-8080-Acceptor-0
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
            t.start();
        }
    }

    // NioEndpoint 类的重写实现,创建一个AbstractEndpoint.Acceptor,实现了 Runnable接口
    // 这里的类 Acceptor 是一个 NioEndpoint 内部类,用于接收套接字并交给合适的处理器
    @Override
    protected AbstractEndpoint.Acceptor createAcceptor() {
        return new Acceptor();
    }    

参考文章

  • Tomcat NIO 基本架构
  • Tomcat NioEndpoint的Acceptor
  • Tomcat NioEndpoint的Poller和PollerEvent
  • Tomcat NioEndpoint的SocketProcessor
  • Tomcat 系统架构与设计模式,第 1 部分
  • Tomcat 系统架构与设计模式,第 2 部分
  • Servlet 工作原理解析
  • spring boot应用启动原理分析
  • Web容器中DefaultServlet详解
  • Jasper 2 JSP Engine How To
  • 深度解读Tomcat中的NIO模型
  • Springboot Web应用Tomcat启动流程概述

你可能感兴趣的:(Tomcat,Spring,Web,Spring,Boot)