1. 程式人生 > 其它 >SpringBoot成長記9:onRefresh如何啟動內嵌的Tomcat容器的?

SpringBoot成長記9:onRefresh如何啟動內嵌的Tomcat容器的?

上一節我們主要分析了refreshContext中,主要有3個邏輯,如下圖:

上一節重點解析了invokeBeanFactoryPostProcessors執行容器擴充套件點,實現了自動裝備配置、第三方執行擴充套件的執行。

今天我們繼續分析refreshContext另一個重要的邏輯onRefresh()邏輯,讓我們開始吧!

快速概覽: onRefresh啟動內嵌tomcat前的操作

refreshContext中onRefresh之前還有一些邏輯,我們先來快速看下它們主要做了什麼。首先來看下程式碼:

    @Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
		      //省略

				// 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();

			//省略
	}

上面主要涉及了3個方法,從名字行就能猜出來它們做了什麼:

1)registerBeanPostProcessors 通過掃描到BeanDefination中找出BeanPostProcessor,增加幾個Bean的擴充套件點BeanPostProcessor 按4類順序逐個增加。

回顧術語BeanPostProcessor是什麼?

之前BeanFactoryPostProcessor是對容器的擴充套件,主要有一個方法,可以給容器設定屬性,補充一些單例物件,補充一些BeanDefinition。

那BeanPostProcessor是對bean的擴充套件,有before和after兩類方法,對Bean如何做擴充套件,在bean的建立前後,給bean補充一些屬性等。

**2)initMessageSource 註冊訊息M essageSource物件到容器 **DelegatingMessageSource 國際化相關支援,預設的沒有。

**3)initApplicationEventMulticaster 註冊廣播物件到容器 ** 這個物件就是之前觸發listener擴充套件點的廣播物件。

熟悉了onRefresh方法之前的大體邏輯後,目前為止,整個rereshConext()執行的邏輯主要如下:

onRefresh的核心脈絡

熟悉了onRefresh方法之前的大體邏輯後,接下來我們就先研究下onRefresh的核心脈絡在做什麼了。

    //ServletWebServerApplicationContext.java
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}
	//父類GenericWebApplicationContext.java
	@Override
	protected void onRefresh() {
		this.themeSource = UiApplicationContextUtils.initThemeSource(this);
	}

這個onRefresh方法的脈絡其實很簡單,父類沒有什麼邏輯,核心應該就是createWebServer了,我們繼續來看下:

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

這個邏輯其實很有意思,主要的核心脈絡是if-else-if

1)如果webServer和servletContext為空,就建立一個WebServer,之後執行initPropertySources。

2)否則就使用getSelfInitializer,執行onStartup方法,之後執行initPropertySources。

可以預設情況webServer和servletContext為空的,這個我們在之前分析的整個流程中沒看到過任何關於這兩個元件的邏輯,或者你自己斷點也能明顯的找到程式碼執行的路徑。

這裡其實你會發現,判斷走哪個分支的方法可能不止一種,你可以連蒙帶猜,也可以斷點走一下,也可以根據經驗分析等等。方法有很多,大家千萬分析原理或者原始碼的時候不要陷入追尋有哪些方法上。方法不是最重要的,適合你自己的就好。這個思想很關鍵,你可以模仿,但不能完全照搬,一定要結合自己情況考慮,最終才能成為你自己的。自己悟到的才是自己的,這些也是思想,也是最關鍵的。所以不要總問我有哪些方法,有時候是你自己悟出來的,我只能提醒或者建議。

好了,言歸正傳,這裡實際走的路徑就是第一條了。如下圖所示:

最終,你會發現onRefresh涉及的核心元件ServletWebServerFactoryWebServerServletContext

SpringBoot對web容器的抽象封裝和設計

既然之前涉及到了幾個元件ServletWebServerFactoryWebServerServletContext。 那它們是分別是什麼東西呢?

其實從名字就能猜出很多東西,不難想到:

ServletContext,這個是指處理整個web請求是的上下文物件,在Tocmat中通常是整個請求的上下文引數都封裝在這個物件中了,非常關鍵的物件。

ServletWebServerFactory和WebServer是什麼?很明顯ServletWebServerFactory是個工廠,用來建立WebServer。

而WebServer從介面中定義的方法就可以看出來,封裝了web容器的啟動和停止,獲取埠的核心操作,也就是說WebServer是web容器的一個抽象封裝。

@FunctionalInterface
public interface ServletWebServerFactory {

   /**
    * Gets a new fully configured but paused {@link WebServer} instance. Clients should
    * not be able to connect to the returned server until {@link WebServer#start()} is
    * called (which happens when the {@code ApplicationContext} has been fully
    * refreshed).
    * @param initializers {@link ServletContextInitializer}s that should be applied as
    * the server starts
    * @return a fully configured and started {@link WebServer}
    * @see WebServer#stop()
    */
   WebServer getWebServer(ServletContextInitializer... initializers);

}
public interface WebServer {

	/**
	 * Starts the web server. Calling this method on an already started server has no
	 * effect.
	 * @throws WebServerException if the server cannot be started
	 */
	void start() throws WebServerException;

	/**
	 * Stops the web server. Calling this method on an already stopped server has no
	 * effect.
	 * @throws WebServerException if the server cannot be stopped
	 */
	void stop() throws WebServerException;

	/**
	 * Return the port this server is listening on.
	 * @return the port (or -1 if none)
	 */
	int getPort();

}

從上面兩個介面的設計和註釋看

首先ServletWebServerFactory的getWebServer註釋翻譯: 獲取一個新的完全配置但暫停的 {@link WebServer} 例項。 客戶應無法連線到返回的伺服器,直到 {@link WebServer#start()} 是呼叫(當 {@code ApplicationContext} 已完全重新整理)。

也就是說,這個方法意思就是獲取到一個配置好的webServer容器,在呼叫start方法時啟動容器,啟動時候ApplicationContext,也就是Spring容器已經完成了重新整理。

WebServer介面封裝了web容器的常見操作,如啟動、停止,獲取埠號之類的。

也就是說ServletWebServerFactory可以獲得一個web容器,WebServer可以操作一個容器。

並且從下面的圖中可以看出,它們有很多不同web容器的實現。整體如下圖所示:

綜上,最終你可以理解為WebServer和ServletWebServerFactory,這一套,其實就是SpringBoot對web容器的抽象封裝,WebServer可以代表了整個容器。

瞭解了onRefesh的整體脈絡和關鍵的元件之後,我們來看下如何建立webServer的。

預設情況我們獲取到的是TomcatServletWebServerFactory,通過它來建立

//TomcatServletWebServerFactory.java
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);
	}

整個方法脈絡如下:

1)入參是一個java8定義的函式表示式,也就是引數傳遞進來了一個方法,使用的是函式式介面ServletContextInitializer。這個方法在後面應該會被執行的。

2)建立了核心元件Tomcat,一會可以看下它的核心脈絡,裡面封裝了Server

3)建立和配置元件Connector, new Connector()、customizeConnector這個是Tomcat的Connector元件相關

4)建立和配置元件Engine getEngine、 configureEngine tomcat的Engine元件相關設定

5)prepareContext 準備tomcat的context相關

6)getTomcatWebServer 真正啟動tomcat

畫成圖如下所示:

看完這個方法後,你可能對這裡涉及的很多元件比較陌生,因為涉及到了很多tomcat的元件。不過沒有關係,你可以通過之前學習的方法來梳理這些元件的關係。就算不知道每個元件是幹嘛的,也可以連蒙帶猜下。

new Tomcat核心元件和脈絡分析

這裡我就來帶教大家一起用之前的方法和思路分析一下它們的核心脈絡吧。

首先第一個元件就是Tomcat這個類的建立。老方法,可以看下這個類脈絡、構造方法之後,畫一個圖。

首先看下構造方法

    public Tomcat() {
        ExceptionUtils.preload();
    }

你會發現什麼都麼有,只有一個異常工具預載入的處理。一看就不是重點。

那就再看下這個類的整體脈絡吧:

看完這個類的脈絡,可以看出來Tomcat這個類主要有
1)對Wrapper相關的操作方法,比如addServelt方法就是返回一個Wrapper。

2)有Context相關一些方法,createContext、addWebapp之類的

3)有一堆元件的get方法,比如Connector、Engine、Service、Server、Host

4)最後就是一些屬性了,比如Server物件、埠號、hostname、使用者許可權相關Userpass/UserRole之類的。

雖然我們不知道這個類裡面的那些元件是幹嘛的。但是起碼我們有了一個印象。可以感受到這個Tomcat類,封裝了幾乎Tomcat所有的核心元件,是一個對tomcat容器的一個抽象物件,代表了整個tomcat。

最後我們可以畫圖先列舉下看完Tomcat這個類的建構函式和類脈絡中,主要涉及概念或者說是元件,如下圖所示:

不知道你們目前是什麼感受,感覺有好多新的概念。如果你不瞭解tomcat的原理的話,第一次看到這一大堆元件,肯定有點懵的。

不過沒關係,你其實可以連蒙帶猜,或者抓大放小,因為我們主要還是看SpringBoot如何啟動內嵌tomcat,如何和tomcat整合Spring容器的。

所以你沒必要非要弄清楚這些元件,等之後我們Tomcat成長記,研究tomcat的原理和原始碼時候再來仔細弄清楚。

這裡我們還是找到關注的重點就可以了。

好,我們接著向下分析。

Connector基本建立和擴充套件設計

最高層的抽象封裝Tomcat物件建立完成後,下一個核心建立的就是Connector了。建立它的程式碼如下:

public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

private String protocol = DEFAULT_PROTOCOL;	

public WebServer getWebServer(ServletContextInitializer... initializers) {
    //其他
    Tomcat tomcat = new Tomcat();
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    //其他
 }

可以看到Connector建立預設傳入了一個Http11NioProtocol類的全名,當然你可以通過set方法修改這個protocol的預設值,只要獲取到TomcatServletWebServerFactory就可以修改對吧?

至於為啥傳遞了類的全名,你猜想下都知道,它內部可能是通過反射建立了這個類,並把這個元件設定給了Connector。我們來看下是不是:

public Connector(String protocol) {
    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
            AprLifecycleListener.getUseAprConnector();

    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
        }
    } else {
        protocolHandlerClassName = protocol;
    }

    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    // Default for Connector depends on this system property
    setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}

這個建構函式最關鍵的就是3行程式碼:

public Connector(String protocol) {
  Class<?> clazz = Class.forName(protocolHandlerClassName);
  p = (ProtocolHandler) clazz.getConstructor().newInstance();
  this.protocolHandler = p;
 }

也就是說其實new Connector核心就做了一件事情:建立了一個Http11NioProtocol元件。

這個從名字上看就是一個NIO相關的通訊元件,內部應該會有Selector、Channel、Bytebuffer等NIO核心元件的。

至於Http11NioProtocol如何建立的這裡我就不帶大家深究了,你可以分析它的建構函式、類脈絡、畫一個元件圖分析下它的建立過程,或者之後我們Tomcat成長記會詳細分析的,可之後帶大家一起分析下。

到這裡先畫個圖小結下:

new Connector之後就是非常關鍵的擴充套件點執行了customizeConnector()方法。

這個方法實際是SpringBoot對Connector擴充套件設計的接入,可以修改Connector中很多配置和屬性,讓我們來一起看下。

private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();

private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>();

protected void customizeConnector(Connector connector) {
		int port = Math.max(getPort(), 0);
		connector.setPort(port);
		if (StringUtils.hasText(this.getServerHeader())) {
			connector.setAttribute("server", this.getServerHeader());
		}
		if (connector.getProtocolHandler() instanceof AbstractProtocol) {
			customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
		}
		invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
		if (getUriEncoding() != null) {
			connector.setURIEncoding(getUriEncoding().name());
		}
		// Don't bind to the socket prematurely if ApplicationContext is slow to start
		connector.setProperty("bindOnInit", "false");
		if (getSsl() != null && getSsl().isEnabled()) {
			customizeSsl(connector);
		}
		TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
		compression.customize(connector);
		for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
			customizer.customize(connector);
		}
}

這個擴充套件方法核心的邏輯就是在給connector進行一些屬性設定,核心通過了兩個擴充套件進行呼叫。

1)invokeProtocolHandlerCustomizers 執行對ProtocolHandler擴充套件

2)customizer.customize(connector); 執行對Connector的擴充套件

其實可以看到觸發的都是tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers這兩個集合中的擴充套件類。、

整體如下圖所示:

我們先不著急看這些擴充套件類做了什麼,首先的得思考下,tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers這兩個集合中的擴充套件類什麼時候設定的值呢?

其實你想下,這兩個屬性屬於誰呢?沒錯,屬於TomcatServletWebServerFactory。而這個類是不是之前通過ServletWebServerApplicationContext執行onRefresh脈絡時候獲取到的呢?如下圖:

對應獲取的程式碼如下:

//ServletWebServerApplicationContext.java
protected ServletWebServerFactory getWebServerFactory() {
   // Use bean names so that we don't consider the hierarchy
   String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
   if (beanNames.length == 0) {
      throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
            + "ServletWebServerFactory bean.");
   }
   if (beanNames.length > 1) {
      throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
            + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
   }
   return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

可以看到的是,這個方法核心就是通過getBean從容器獲取一個物件。但是其實容器中並沒有ServletWebServerFactory這個物件,只有它的BeanDefinition。

為什麼呢?因為之前我們執行ConfigurationClassPostProcessor時候,只是載入了Bean對應的BeanDefinition而已。

不過沒關係,getBean中的邏輯是,如果容器沒有但是有對應的BeanDefinition,它會進行Bean例項化,Bean的例項化我們下一節會詳細講,這裡只是簡單提下。

那這個bean,ServletWebServerFactory例項化的時候會做什麼呢?除了基本建構函式外,其實Bean例項化的過程有很多擴充套件點,可以為bean設定屬性。

好,那關鍵的就來了,tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers既然是ServletWebServerFactory的兩個屬性,肯定就可以通過Bean例項化時候的擴充套件點,給這兩個屬性設定進去值。

最終,我給大家概況如下圖所示:

至於Connector中每個customizer做了哪些事情,這裡我們不去詳細分析了

大體就是初始化protocol相關的配置,比如setMaxThreads預設200、minSpareThreads預設10、maxHttpHeaderSize預設8192byte 、maxSwallowSize 2097152等等。

熟悉了這個擴充套件點的邏輯後,其實最關鍵的是如何使用它,你可以通過ServerProperties擴充套件配置值,也可以自定義tomcatConnectorCustomizers或者tomcatProtocolHandlerCustomizers,只要實現對應的介面就可以了。這個才是領悟了SpringBoot的設計思路後最關鍵的。

術語普及:Tomcat的Engine、Context、Host、Wrapper關係

分析完了Connector的建立之後,其他的元件其實就是普通的建立,建立關聯關係而已。它們的關係其實不復雜,屬於tomcat的基本知識,這裡我通過一個tomcat流程執行圖給大家介紹下它們的關係即可。它們之間的關係如下圖所示:

這些元件每個都有自己的職責,你大體瞭解上述元件的關係就可以了,我們就不展開分析了。

當然SpringBoot也有一些對它們的擴充套件,比如對Engine、Context閥門的擴充套件。也是通過engineValves、contextValves兩個list屬性進行擴充套件。

//TomcatServletWebServerFactory.java
private List<Valve> engineValves = new ArrayList<>();

private List<Valve> contextValves = new ArrayList<>();

只不過這兩個集合預設是空的,你可以通過TomcatServletWebServerFactory對他們進行設定和擴充套件。

這裡我也不展開了。

記住,只要你理解了SpringBoot圍繞TomcatServletWebServerFactory對tomcat做封裝和擴充套件是關鍵,就可以了。

prepareContext 中的擴充套件點ServletContextInitializer

前面一堆元件建立完成後,還有一個比較有意思的操作就是prepareContext 。

讓我們來看下吧!它的程式碼如下:

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
   File documentRoot = getValidDocumentRoot();
   TomcatEmbeddedContext context = new TomcatEmbeddedContext();
   if (documentRoot != null) {
      context.setResources(new LoaderHidingResourceRoot(context));
   }
   context.setName(getContextPath());
   context.setDisplayName(getDisplayName());
   context.setPath(getContextPath());
   File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
   context.setDocBase(docBase.getAbsolutePath());
   context.addLifecycleListener(new FixContextListener());
   context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
         : ClassUtils.getDefaultClassLoader());
   resetDefaultLocaleMapping(context);
   addLocaleMappings(context);
   context.setUseRelativeRedirects(false);
   try {
      context.setCreateUploadTargets(true);
   }
   catch (NoSuchMethodError ex) {
      // Tomcat is < 8.5.39. Continue.
   }
   configureTldSkipPatterns(context);
   WebappLoader loader = new WebappLoader(context.getParentClassLoader());
   loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
   loader.setDelegate(true);
   context.setLoader(loader);
   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);
   postProcessContext(context);
}

整個方法的脈絡其實不復雜,主要就是:

1)new TomcatEmbeddedContext

2)為tomcat的這個Context設定了很多值

3)執行了一個擴充套件點ServletContextInitializer

整體如下圖所示:

至於擴充套件點,ServletContextInitializer執行了什麼?

其實可以看下它的邏輯。它是使用了java8的特性,通過一個函式式介面傳入過來的方法,也就是說,通過方法引數傳遞過來了一個行為,而不是一個變數。

我們可以找到傳入的位置:

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

方法其實不復雜,核心就是觸發了ServletContextInitializer的所有實現,執行了擴充套件方法onStartup。

預設主要有4個實現:

result = {ServletContextInitializerBeans@6345}  size = 4
 0 = {DispatcherServletRegistrationBean@6339} "dispatcherServlet urls=[/]"
 1 = {FilterRegistrationBean@6350} "characterEncodingFilter urls=[/*] order=-2147483648"
 2 = {FilterRegistrationBean@6351} "formContentFilter urls=[/*] order=-9900"
 3 = {FilterRegistrationBean@6352} "requestContextFilter urls=[/*] order=-105"

其實從名字就看出來了,它的含義是往ServletContext中註冊一堆Servelt、Filter等等。

這個擴充套件點還是比較關鍵的。

整體如下圖所示:

思考:tomcat和SpringBoot怎麼整合的?

分析完了整個WebServer的建立後,其實你就會發現:

最終是Spring的容器ServletWebServerApplicationContext建立了WebServer,它持有了這物件,也就有了Tomcat整個抽象封裝。

自然它們就整合到了一起了。

內嵌Tomcat最終的啟動

之前分析的整個邏輯都是webServer這個物件的建立,之前從註釋我們就知道,建立的webServer只是一個配置完成,停止的web容器,web容器並沒有啟動。只有呼叫webServer#start()這個方法,容器才會真正啟動。

所以,最後我們來分析下tomcat是如何啟動的。啟動的程式碼就是getWebServer的最後一行,程式碼如下:

//TomcatServletWebServerFactory.java
    @Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
    	//省略 Tomcat的建立、connector的建立和擴充套件、其他元件的建立、prepareContext的執行和擴充套件
		return getTomcatWebServer(tomcat);
	}
	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();
    }

可以看到上面的程式碼脈絡是:通過一系列的方法呼叫最終將建立的tomcat物件,有封裝了一下,封裝為了TomcatWebServer物件,之後執行了initialize()。

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

上面方法邏輯看似多,其實最關鍵的就一句話。這裡核心是你抓大放小,主要關注一句話就可以了:

tomcat.start();

這個start方法執行的流程很有意思。它是類似一個鏈式呼叫。

其實你從之前tomcat的元件圖就可以猜到,它們元件層級關係很多,每個元件都會觸發下一層元件的邏輯。

每個元件都有生命週期,比如init方法-->start()-->destory()之類的。

那麼也就說tomcat會以鏈的方式逐級呼叫各個模組的init()方法進行初始化, 待各個模組都初始化後, 又會逐級呼叫各個模組的start()方法啟動各個模組。

整體大概如下圖所示:

小結

最後我們小結下,今天我們主要分析了SpringBoot在onRefresh方法中如何啟動的tomcat:

1)快速該來了 onRefresh啟動內嵌tomcat前的操作

2)分析了onRefresh的核心脈絡

3)思考了SpringBoot對web容器的抽象封裝和設計

4)對new Tomcat進行了核心元件和脈絡分析

5)分析了Connector基本建立和擴充套件設計

6)術語普及:Tomcat的Engine、Context、Host、Wrapper關係

7)prepareContext 中的擴充套件點ServletContextInitializer

8)思考了:tomcat和SpringBoot怎麼整合的?

9)內嵌Tomcat最終的啟動

最後補充一點思想:

整個過程中有的是知識點,有的是一些設計的思考、擴充套件點的思考。大家一定要學會抓重點。這個能力非常關鍵。

這就是涉及到了一個能力模型的層次,可以有這樣一種劃分

第一個層次:知識、技術基本的使用,也就是說我們從瞭解知識到使用它做好一件事是一個層次。這個可以體現在你學習技術上,帶領團隊做專案,或者學習任何新事物上。

第二個層次:通過思考和總結,抽象和提煉出事情的關鍵點。比如一個提煉設計思想,發現專案的關鍵節點、路徑等。

最後一個層次:站在一定高度和視角,掌控和推進整體。這個就需要很多經驗和像比你成功的人學習了,因為掌握力是可以練習的,但是視野和思想的高度,雖然可以經驗積累,但是最快的方式就是像比你優秀的人學習,如果有一個導師的話那就更好了。

本文由部落格群發一文多發等運營工具平臺 OpenWrite 釋出