深度解析SpringBoot內嵌Web容器

2023-06-26 18:00:59

你好,我是劉牌!

前言

今天分享一個SpringBoot的內嵌Web容器,在SpringBoot還沒有出現時,我們使用Java開發了Web專案,需要將其部署到Tomcat下面,需要設定很多xml檔案,SpringBoot出現後,就從繁瑣的xml檔案中解脫出來了,SpringBoot將Web容器進行了內嵌,我們只需要將專案打成一個jar包,就可以執行了,大大省略了開發成本,那麼SpringBoot是怎麼實現的呢,我們今天就來詳細介紹。

SpringBoot提供的內嵌容器

SpringBoot提供了四種Web容器,分別為Tomcat,Jetty,Undertow,Netty。

Tomcat

Spring Boot 預設使用 Tomcat 作為嵌入式 Web 容器。Tomcat 作為一個流行的 Web 容器,容易能夠理解、設定和管理。可以通過使用spring-boot-starter-web來啟用 Tomcat 容器。

Jetty

Jetty 同樣是一個流行的嵌入式 Web 容器,它的預設設定相對精簡,從而有利快速啟動。可以通過使用spring-boot-starter-jetty來啟用 Jetty 容器。

Undertow

Undertow 是一個由 JBoss 開發的輕量級的嵌入式 Web 伺服器。它具有出色的效能和低資源佔用率,是一個適合微服務實現的 Web 伺服器。可以使用spring-boot-starter-undertow來啟用 Undertow 容器。

Netty

Netty是一個高效能的網路框架,需要引入spring-boot-starter-webflux和spring-boot-starter-reactor-netty來開啟Netty作為Web容器。

使用

因為SpringBoot預設的是Tomcat作為Web容器,如果我們需要使用使用其他Web容器,那麼需要排除Tomcat容器,再引入其他容器,Tomcat容器位於spring-boot-starter-web模組下,所以我們需要在maven的pom.xml中移除Tomcat,如下。

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>3.0.2</version>
      <exclusions>
          <exclusion>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-tomcat</artifactId>
          </exclusion>
      </exclusions>
</dependency>

然後引入對應的Web容器,比如引入Undertow

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

然後可以在yml檔案中設定相應容器的引數,如下設定undertow.

server:
  port: 8080
  undertow:
    threads:
      worker: 10
      io: 10
    direct-buffers: true

其他web容器可以根據實際情況設定,從ServerProperties組態檔中可以檢視對應的Web容器的相關設定。

原始碼解析

下面從原始碼進行分析,我們先使用SpringBoot的預設Web容器Tomcat進行分析。

那麼原始碼應該從哪裡看起呢,對於SpringBoot這麼龐大複雜的專案,首先,我們在使用SpringBoot的時候,需要在application.yml檔案中設定相關資訊,比如埠,如果不設定埠,預設是8080,那麼這個埠肯定是web容器的埠,如果是Tomcat,那麼Tomcat就設定為這個埠,Undertow也是,依此類推。

那麼這裡就是一個入口,在SpringBoot中,我們要獲取yml檔案中的設定資訊,一般是通過@ConfigurationProperties註解,我們可以按住ctrl,然後滑鼠點選這個port,就能跳到對應的屬性類裡面。

屬性類ServerProperties就是專門獲取yml檔案中的設定,然後以供使用。

到了屬性類裡面後,我們繼續ctrl,然後會彈出很多類,如下所示。

因為我們使用的是Tomcat,那麼就選擇一個Tomcat相關的類,我們選擇TomcatWebServerFactoryCustomizer,這個類實現了介面WebServerFactoryCustomizer,並實現了方法customize。

customize的引數是ConfigurableTomcatWebServerFactory,它是一個介面,它還繼承了介面ConfigurableWebServerFactory,我們從ConfigurableWebServerFactory中看出裡面有設定埠,地址等方法。

我們再回頭看ConfigurableTomcatWebServerFactory,可以看出裡面是一些Tomcat相關的方法。

然後繼續看ConfigurableUndertowWebServerFactory,可以看出裡面是對Undertow的一些屬性設定的方法。

我們回到TomcatWebServerFactoryCustomizer類中,SpringBoot使用了它的PropertyMapper類對屬性進行設定,我們可以看出它使用propertyMapper.from().to()語法,其實就是將ServerProperties中的屬性設定到ConfigurableTomcatWebServerFactory中,這個屬性設定是在Spring對Bean進行初始化時候設定的,使用的是Spring的後置處理器來實現的,後面我們繼續說。

然後我們繼續看一下TomcatWebServerFactoryCustomizer,他有一個建構函式,引數是Environment和ServerProperties,那麼就證明其他地方對其進行了new操作。

我們也是用ctrl套路,點選建構函式後跳到了EmbeddedWebServerFactoryCustomizerAutoConfiguration自動裝配類中,這個類中有四個靜態類,我們可以看出,他們的作用都是建立對應的客製化器Bean,其實就是將yml檔案中的Web容器設定進行裝配,以供後面使用。

上面說的這一堆其實就是SpringBoot的自動裝配,其目的就是建立對應的Customizer,因為每個Web容器的設定項不一樣,所以就需要不同的Customizer和Factory。

上面說了這麼多,怎麼感覺和原始碼沒關係呢,沒錯,其實上面說的並不是核心原始碼,那麼怎麼找到核心原始碼呢?我們思考一下,既然上面是部分原始碼,那麼原始碼肯定會執行到這裡。

檢視呼叫鏈

我們在上面的TomcatWebServerFactoryCustomizer類中的customize方法中打一個斷點,然後debug,於是得到呼叫鏈如下。

我們可以看出會呼叫onRefresh()方法,因為AbstractApplicationContext使用的是模板方法模式,具體的實現交給子類實現,因為使用的是Tomcat,所以交給了ServletWebServerApplicationContext類來實現,具體的子類裡面有一個createWebServer()方法,它就是建立Web容器。

具體實現如下,如下是Tomcat的實現,裡面會涉及到兩個重要的介面WebServerWebServerFactory

WebServer

WebServer是容器的頂層介面,具體實現交給具體的容器實現類,如Tomcat則使用TomcatWebServer,Undertow則使用UndertowWebServer,Jetty,Netty也是如此。

此介面提供了一些方法,start()啟動Web伺服器,stop()停止Web伺服器,getPort()獲取伺服器埠。

不過對於start()和stop(),它們只是介面抽象的規範,在具體的實現中,也並不是全部都按照這個標準,start()方法上有備註Starts the web server. Calling this method on an already started server has no effect.,翻譯為:啟動web伺服器。在已啟動的伺服器上呼叫此方法無效。,比如Tomcat的就沒有在start()方法中啟動伺服器,具體我們等會會看。

WebServerFactory

WebServerFactory是一個介面,沒有定義任何方法,它就建立Web伺服器的工廠的標記介面,Spring中很多地方也是這樣的風格。

這個介面重要的兩個子介面,也是我們需要關注的兩個子介面分別是ServletWebServerFactoryReactiveWebServerFactory,它們兩個都定義了一個方法getWebServer

JettyUndertowTomcat三個都屬於Servlet容器,所以使用的是ServletWebServerFactory來建立Web容器。

Netty不是Servlet容器,所以使用的是ReactiveWebServerFactory來建立Web容器。

上面對這兩個介面進行了介紹,基本上整個Web容器都是圍繞這兩個介面來,我們下面繼續分析。

獲取WebServerFactory

首先我們要先獲取web服務的工廠類的Bean,才能建立Web容器,因為我們使用的是Tomcat,所以獲取到的工廠類是TomcatServletWebServerFactory,具體的獲取Bean的過程我們就沒有必要去一一說明,只要對Spring IOC稍微熟悉一點就能理解,我們主要說一下在後置處理器。

上面我們介紹了Tomcat容器的客製化器Customizer,裡面對Web容器的設定屬性進行組裝,它就是發生在Bean的初始化前,用到的Bean後置處理器是WebServerFactoryCustomizerBeanPostProcessor

Bean的後置處理器中,會呼叫對應的客製化器,Tomcat呼叫的就是TomcatWebServerFactoryCustomizer,其他的也一樣,其目的都是客製化WebServerFactory。

經過一系列處理後,就從IOC容器中獲取到了WebServerFactoryBean,然後再使用這個工廠去建立Web服務。

建立Web服務

獲取到WebServerFactory後,就可以建立Web容器,因為使用的是Tomcat,所以使用的是TomcatServletWebServerFactory,如下,我們就看到了Tomcat的身影。

最後啟動Tomcat容器是在TomcatWebServer中,在TomcatWebServer的建構函式中呼叫initialize(),在initialize()中我們看是this.tomcat.start(),Tomcat被啟動了。

上面我們在說WebServer介面的時候,說了啟動start()方法,在Tomcat的實現中就沒有使用start()來啟動容器,但是在Undertow中,就使用了start()方法來啟動容器。

Undertow容器啟動

上面我們介紹了Tomcat容器的建立,Undertow的流程和Tomcat基本上是一樣的,但是在啟動的時候,Undertow是在start()方法中啟動,而start()方法需要在
finishRefresh()這一步中執行。

在finishRefresh()中,會呼叫生命週期處理器

最終會走到WebServerStartStopLifecycle這個生命週期,這裡就會呼叫WebServer中的start()方法。

最終在UndertowWebServer中啟動Undertow容器

具體執行順序如下。

finishRefresh() -> getLifecycleProcessor().onRefresh() -> startBeans(true) -> start() -> doStart(this.lifecycleBeans, member.name, this.autoStartupOnly) -> bean.start() -> this.webServer.start()

上面我們分析了Tomcat和Undertow的建立流程,Jetty和Netty也是大同小異,因為Spring使用了模板方法模式,具體的實現交給具體的Web容器,所以在整體結構上是差不多的,只是實現方式不同。

總結

關於SpringBoot的內嵌Web容器,就說得差不多了,我們從各種Web容器進行介紹,包括他們的有點,怎麼在SpringBoot中使用,並對原始碼進行解析,在原始碼解析這裡,我們並沒有進行芝麻細節式解析,而是從大體上進行解析,只有對大致結構瞭解,才能更好地進行深度學習。

SpringBoot內嵌容器涉及的知識點還是比較多,需要對Spring和SpringBoot有一定的瞭解才能更好地學習它,本文基於SpringBoot3.0進行解析,
SpringBoot3.0中,Servlet也是遵循Jakata EE規範。

今天的分享就到這裡,感謝你的觀看,我們下期見,如果文中有不對或者不合理的地方,希望得到你的指點,我們一起在學習中成長,一起在成長中學習。