你好,我是劉牌!
前言今天分享一個(gè)SpringBoot的內(nèi)嵌Web容器,在SpringBoot還沒(méi)有出現(xiàn)時(shí),我們使用Java開(kāi)發(fā)了Web項(xiàng)目,需要將其部署到Tomcat下面,需要配置很多xml文件,SpringBoot出現(xiàn)后,就從繁瑣的xml文件中解脫出來(lái)了,SpringBoot將Web容器進(jìn)行了內(nèi)嵌,我們只需要將項(xiàng)目打成一個(gè)jar包,就可以運(yùn)行了,大大省略了開(kāi)發(fā)成本,那么SpringBoot是怎么實(shí)現(xiàn)的呢,我們今天就來(lái)詳細(xì)介紹。
(資料圖片)
SpringBoot提供的內(nèi)嵌容器SpringBoot提供了四種Web容器,分別為T(mén)omcat,Jetty,Undertow,Netty。
TomcatSpring Boot 默認(rèn)使用 Tomcat 作為嵌入式 Web 容器。Tomcat 作為一個(gè)流行的 Web 容器,容易能夠理解、配置和管理。可以通過(guò)使用spring-boot-starter-web來(lái)啟用 Tomcat 容器。
JettyJetty 同樣是一個(gè)流行的嵌入式 Web 容器,它的缺省配置相對(duì)精簡(jiǎn),從而有利快速啟動(dòng)??梢酝ㄟ^(guò)使用spring-boot-starter-jetty來(lái)啟用 Jetty 容器。
UndertowUndertow 是一個(gè)由 JBoss 開(kāi)發(fā)的輕量級(jí)的嵌入式 Web 服務(wù)器。它具有出色的性能和低資源占用率,是一個(gè)適合微服務(wù)實(shí)現(xiàn)的 Web 服務(wù)器??梢允褂胹pring-boot-starter-undertow來(lái)啟用 Undertow 容器。
NettyNetty是一個(gè)高性能的網(wǎng)絡(luò)框架,需要引入spring-boot-starter-webflux和spring-boot-starter-reactor-netty來(lái)開(kāi)啟Netty作為Web容器。
使用因?yàn)镾pringBoot默認(rèn)的是Tomcat作為Web容器,如果我們需要使用使用其他Web容器,那么需要排除Tomcat容器,再引入其他容器,Tomcat容器位于spring-boot-starter-web模塊下,所以我們需要在maven的pom.xml中移除Tomcat,如下。
org.springframework.boot spring-boot-starter-web 3.0.2 org.springframework.boot spring-boot-starter-tomcat
然后引入對(duì)應(yīng)的Web容器,比如引入U(xiǎn)ndertow
org.springframework.boot spring-boot-starter-undertow
然后可以在yml文件中配置相應(yīng)容器的參數(shù),如下配置undertow.
server: port: 8080 undertow: threads: worker: 10 io: 10 direct-buffers: true
其他web容器可以根據(jù)實(shí)際情況配置,從ServerProperties配置文件中可以查看對(duì)應(yīng)的Web容器的相關(guān)配置。
源碼解析下面從源碼進(jìn)行分析,我們先使用SpringBoot的默認(rèn)Web容器Tomcat進(jìn)行分析。
那么源碼應(yīng)該從哪里看起呢,對(duì)于SpringBoot這么龐大復(fù)雜的項(xiàng)目,首先,我們?cè)谑褂肧pringBoot的時(shí)候,需要在application.yml文件中配置相關(guān)信息,比如端口,如果不配置端口,默認(rèn)是8080,那么這個(gè)端口肯定是web容器的端口,如果是Tomcat,那么Tomcat就設(shè)置為這個(gè)端口,Undertow也是,依此類(lèi)推。
那么這里就是一個(gè)入口,在SpringBoot中,我們要獲取yml文件中的配置信息,一般是通過(guò)@ConfigurationProperties
注解,我們可以按住ctrl,然后鼠標(biāo)點(diǎn)擊這個(gè)port,就能跳到對(duì)應(yīng)的屬性類(lèi)里面。
屬性類(lèi)ServerProperties就是專(zhuān)門(mén)獲取yml文件中的配置,然后以供使用。
到了屬性類(lèi)里面后,我們繼續(xù)ctrl,然后會(huì)彈出很多類(lèi),如下所示。
因?yàn)槲覀兪褂玫氖荰omcat,那么就選擇一個(gè)Tomcat相關(guān)的類(lèi),我們選擇TomcatWebServerFactoryCustomizer
,這個(gè)類(lèi)實(shí)現(xiàn)了接口WebServerFactoryCustomizer
,并實(shí)現(xiàn)了方法customize。
customize的參數(shù)是ConfigurableTomcatWebServerFactory
,它是一個(gè)接口,它還繼承了接口ConfigurableWebServerFactory
,我們從ConfigurableWebServerFactory
中看出里面有設(shè)置端口,地址等方法。
我們?cè)倩仡^看ConfigurableTomcatWebServerFactory
,可以看出里面是一些Tomcat相關(guān)的方法。
然后繼續(xù)看ConfigurableUndertowWebServerFactory
,可以看出里面是對(duì)Undertow的一些屬性設(shè)置的方法。
我們回到TomcatWebServerFactoryCustomizer
類(lèi)中,SpringBoot使用了它的PropertyMapper
類(lèi)對(duì)屬性進(jìn)行設(shè)置,我們可以看出它使用propertyMapper.from().to()語(yǔ)法,其實(shí)就是將ServerProperties中的屬性設(shè)置到ConfigurableTomcatWebServerFactory中,這個(gè)屬性設(shè)置是在Spring對(duì)Bean進(jìn)行初始化時(shí)候設(shè)置的,使用的是Spring的后置處理器來(lái)實(shí)現(xiàn)的,后面我們繼續(xù)說(shuō)。
然后我們繼續(xù)看一下TomcatWebServerFactoryCustomizer
,他有一個(gè)構(gòu)造函數(shù),參數(shù)是Environment和ServerProperties,那么就證明其他地方對(duì)其進(jìn)行了new操作。
我們也是用ctrl套路,點(diǎn)擊構(gòu)造函數(shù)后跳到了EmbeddedWebServerFactoryCustomizerAutoConfiguration
自動(dòng)裝配類(lèi)中,這個(gè)類(lèi)中有四個(gè)靜態(tài)類(lèi),我們可以看出,他們的作用都是創(chuàng)建對(duì)應(yīng)的定制器Bean,其實(shí)就是將yml文件中的Web容器配置進(jìn)行裝配,以供后面使用。
上面說(shuō)的這一堆其實(shí)就是SpringBoot的自動(dòng)裝配,其目的就是創(chuàng)建對(duì)應(yīng)的Customizer,因?yàn)槊總€(gè)Web容器的配置項(xiàng)不一樣,所以就需要不同的Customizer和Factory。
查看調(diào)用鏈上面說(shuō)了這么多,怎么感覺(jué)和源碼沒(méi)關(guān)系呢,沒(méi)錯(cuò),其實(shí)上面說(shuō)的并不是核心源碼,那么怎么找到核心源碼呢?我們思考一下,既然上面是部分源碼,那么源碼肯定會(huì)執(zhí)行到這里。
我們?cè)谏厦娴?code>TomcatWebServerFactoryCustomizer類(lèi)中的customize
方法中打一個(gè)斷點(diǎn),然后debug,于是得到調(diào)用鏈如下。
我們可以看出會(huì)調(diào)用onRefresh()
方法,因?yàn)?code>AbstractApplicationContext使用的是模板方法模式,具體的實(shí)現(xiàn)交給子類(lèi)實(shí)現(xiàn),因?yàn)槭褂玫氖荰omcat,所以交給了ServletWebServerApplicationContext
類(lèi)來(lái)實(shí)現(xiàn),具體的子類(lèi)里面有一個(gè)createWebServer()
方法,它就是創(chuàng)建Web容器。
具體實(shí)現(xiàn)如下,如下是Tomcat的實(shí)現(xiàn),里面會(huì)涉及到兩個(gè)重要的接口WebServer
和WebServerFactory
。
WebServer是容器的頂層接口,具體實(shí)現(xiàn)交給具體的容器實(shí)現(xiàn)類(lèi),如Tomcat則使用TomcatWebServer
,Undertow則使用UndertowWebServer
,Jetty,Netty也是如此。
此接口提供了一些方法,start()啟動(dòng)Web服務(wù)器,stop()停止Web服務(wù)器,getPort()獲取服務(wù)器端口。
不過(guò)對(duì)于start()和stop(),它們只是接口抽象的規(guī)范,在具體的實(shí)現(xiàn)中,也并不是全部都按照這個(gè)標(biāo)準(zhǔn),start()方法上有備注Starts the web server. Calling this method on an already started server has no effect.
,翻譯為:啟動(dòng)web服務(wù)器。在已啟動(dòng)的服務(wù)器上調(diào)用此方法無(wú)效。
,比如Tomcat的就沒(méi)有在start()方法中啟動(dòng)服務(wù)器,具體我們等會(huì)會(huì)看。
WebServerFactory是一個(gè)接口,沒(méi)有定義任何方法,它就創(chuàng)建Web服務(wù)器的工廠的標(biāo)記接口,Spring中很多地方也是這樣的風(fēng)格。
這個(gè)接口重要的兩個(gè)子接口,也是我們需要關(guān)注的兩個(gè)子接口分別是ServletWebServerFactory
和ReactiveWebServerFactory
,它們兩個(gè)都定義了一個(gè)方法getWebServer
。
Jetty
,Undertow
,Tomcat
三個(gè)都屬于Servlet容器,所以使用的是ServletWebServerFactory
來(lái)創(chuàng)建Web容器。
而Netty
不是Servlet容器,所以使用的是ReactiveWebServerFactory
來(lái)創(chuàng)建Web容器。
獲取WebServerFactory上面對(duì)這兩個(gè)接口進(jìn)行了介紹,基本上整個(gè)Web容器都是圍繞這兩個(gè)接口來(lái),我們下面繼續(xù)分析。
首先我們要先獲取web服務(wù)的工廠類(lèi)的Bean,才能創(chuàng)建Web容器,因?yàn)槲覀兪褂玫氖荰omcat,所以獲取到的工廠類(lèi)是TomcatServletWebServerFactory
,具體的獲取Bean的過(guò)程我們就沒(méi)有必要去一一說(shuō)明,只要對(duì)Spring IOC稍微熟悉一點(diǎn)就能理解,我們主要說(shuō)一下在后置處理器。
上面我們介紹了Tomcat容器的定制器Customizer,里面對(duì)Web容器的配置屬性進(jìn)行組裝,它就是發(fā)生在Bean的初始化前,用到的Bean后置處理器是WebServerFactoryCustomizerBeanPostProcessor
。
Bean的后置處理器中,會(huì)調(diào)用對(duì)應(yīng)的定制器,Tomcat調(diào)用的就是TomcatWebServerFactoryCustomizer
,其他的也一樣,其目的都是定制WebServerFactory。
經(jīng)過(guò)一系列處理后,就從IOC容器中獲取到了WebServerFactory
Bean,然后再使用這個(gè)工廠去創(chuàng)建Web服務(wù)。
獲取到WebServerFactory后,就可以創(chuàng)建Web容器,因?yàn)槭褂玫氖荰omcat,所以使用的是TomcatServletWebServerFactory
,如下,我們就看到了Tomcat的身影。
最后啟動(dòng)Tomcat容器是在TomcatWebServer中,在TomcatWebServer的構(gòu)造函數(shù)中調(diào)用initialize(),在initialize()中我們看是this.tomcat.start(),Tomcat被啟動(dòng)了。
上面我們?cè)谡f(shuō)WebServer
接口的時(shí)候,說(shuō)了啟動(dòng)start()
方法,在Tomcat的實(shí)現(xiàn)中就沒(méi)有使用start()
來(lái)啟動(dòng)容器,但是在Undertow中,就使用了start()
方法來(lái)啟動(dòng)容器。
上面我們介紹了Tomcat容器的創(chuàng)建,Undertow的流程和Tomcat基本上是一樣的,但是在啟動(dòng)的時(shí)候,Undertow是在start()方法中啟動(dòng),而start()方法需要在finishRefresh()這一步中執(zhí)行。
在finishRefresh()中,會(huì)調(diào)用生命周期處理器
最終會(huì)走到WebServerStartStopLifecycle這個(gè)生命周期,這里就會(huì)調(diào)用WebServer中的start()方法。
最終在UndertowWebServer中啟動(dòng)Undertow容器
具體執(zhí)行順序如下。
finishRefresh() -> getLifecycleProcessor().onRefresh() -> startBeans(true) -> start() -> doStart(this.lifecycleBeans, member.name, this.autoStartupOnly) -> bean.start() -> this.webServer.start()
總結(jié)上面我們分析了Tomcat和Undertow的創(chuàng)建流程,Jetty和Netty也是大同小異,因?yàn)镾pring使用了模板方法模式,具體的實(shí)現(xiàn)交給具體的Web容器,所以在整體結(jié)構(gòu)上是差不多的,只是實(shí)現(xiàn)方式不同。
關(guān)于SpringBoot的內(nèi)嵌Web容器,就說(shuō)得差不多了,我們從各種Web容器進(jìn)行介紹,包括他們的有點(diǎn),怎么在SpringBoot中使用,并對(duì)源碼進(jìn)行解析,在源碼解析這里,我們并沒(méi)有進(jìn)行芝麻細(xì)節(jié)式解析,而是從大體上進(jìn)行解析,只有對(duì)大致結(jié)構(gòu)了解,才能更好地進(jìn)行深度學(xué)習(xí)。
SpringBoot內(nèi)嵌容器涉及的知識(shí)點(diǎn)還是比較多,需要對(duì)Spring和SpringBoot有一定的了解才能更好地學(xué)習(xí)它,本文基于SpringBoot3.0進(jìn)行解析,SpringBoot3.0中,Servlet也是遵循Jakata EE規(guī)范。
今天的分享就到這里,感謝你的觀看,我們下期見(jiàn),如果文中有不對(duì)或者不合理的地方,希望得到你的指點(diǎn),我們一起在學(xué)習(xí)中成長(zhǎng),一起在成長(zhǎng)中學(xué)習(xí)。
標(biāo)簽: