Apache的Tomcat應(yīng)用服務(wù)器是Servlet,JSP,EL(Expression Language)和WebSocket技術(shù)的開(kāi)源實(shí)現(xiàn)。而Servlet,JSP,EL(表達(dá)式語(yǔ)言)和WebSocket規(guī)范是在JCP(Java社區(qū)進(jìn)程)下開(kāi)發(fā)的。Tomcat是在開(kāi)放和參與的環(huán)境中開(kāi)發(fā)的,并在第二版的Apache許可協(xié)議下發(fā)布。
Tomcat旨在成為來(lái)自世界各地最優(yōu)秀的開(kāi)發(fā)人員的合作伙伴,所以參與Tomcat項(xiàng)目者甚眾。同時(shí)為不同行業(yè)和組織的眾多大型、核心關(guān)鍵的Web應(yīng)用程序提供強(qiáng)力支持。其世面上的Java Web應(yīng)用服務(wù)器的份額多年來(lái)一直占第一名,在50%左右。
為了更好的理解和把握作為這么受歡迎的應(yīng)用服務(wù)器,我們一起來(lái)從其架構(gòu)和設(shè)計(jì)實(shí)現(xiàn)上更深入的剖析,以饗讀者和研發(fā)工程師。本文主要感謝許令波先生的分享貢獻(xiàn)。
本文主要以 Tomcat 5 -6為基礎(chǔ)展開(kāi),因?yàn)?Tomcat 的基本設(shè)計(jì)思路和架構(gòu)是具有一定連續(xù)性的,其最新版本的服務(wù)器設(shè)計(jì)和實(shí)現(xiàn),主要基于上一版本的功能完善、性能優(yōu)化等方面推進(jìn),以便實(shí)現(xiàn)對(duì)新規(guī)范的支持。下面就來(lái)學(xué)習(xí)一下架構(gòu)設(shè)計(jì)和實(shí)現(xiàn)原理,沒(méi)準(zhǔn)各位看官可以自己動(dòng)手來(lái)實(shí)現(xiàn)一版Tomcat呢。
一、Tomcat 總體結(jié)構(gòu)
Tomcat 的結(jié)構(gòu)很復(fù)雜,但是 Tomcat 也非常的模塊化,找到了 Tomcat 最核心的模塊,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的總體結(jié)構(gòu)圖:
圖 1.Tomcat 的總體結(jié)構(gòu)
從上圖中可以看出 Tomcat 的心臟是兩個(gè)組件:Connector 和 Container,關(guān)于這兩個(gè)組件將在后面詳細(xì)介紹。Connector 組件是可以被替換,這樣可以提供給服務(wù)器設(shè)計(jì)者更多的選擇,因?yàn)檫@個(gè)組件是如此重要,不僅跟服務(wù)器的設(shè)計(jì)的本身,而且和不同的應(yīng)用場(chǎng)景也十分相關(guān),所以一個(gè) Container 可以選擇對(duì)應(yīng)多個(gè) Connector。多個(gè) Connector 和一個(gè) Container 就形成了一個(gè) Service,Service 的概念大家都很熟悉了,有了Service 就可以對(duì)外提供服務(wù)了,但是 Service 還要一個(gè)生存的環(huán)境,必須要有人能夠給她生命、掌握其生死大權(quán),那就非 Server 莫屬了。所以整個(gè) Tomcat 的生命周期由 Server 控制。
1.1以 Service 為"界"
我們將 Tomcat 中 Connector、Container 簡(jiǎn)單作為一個(gè)整體比作一個(gè)家庭的男女主人的話,Connector 主要負(fù)責(zé)對(duì)外交流(主外),可以比作為男人,Container 主要處理 Connector 接受的請(qǐng)求,主要是處理內(nèi)部事務(wù)(主內(nèi)),可以比作為 女人。那么這個(gè) Service就是民政局,它結(jié)婚證的形式促成了或連接這對(duì)男女家庭的存在。即是 Service 將它們連接在一起,共同組成一個(gè)家庭。當(dāng)然要組成一個(gè)家庭還要很多其它的元素。說(shuō)白了,Service 只是在 Connector 和 Container 外面多包一層,把它們組裝在一起,向外面提供服務(wù),一個(gè) Service 可以設(shè)置多個(gè) Connector,但是只能有一個(gè) Container 容器。Service 接口的方法如下圖所示:
圖 2. Service 接口
從 Service 接口中定義的方法中可以看出,它主要是為了關(guān)聯(lián) Connector 和Container,同時(shí)會(huì)初始化它下面的其它組件,注意接口中它并沒(méi)有規(guī)定一定要控制它下面的組件的生命周期。所有組件的生命周期在一個(gè) Lifecycle 的接口中控制,這里用到了一個(gè)重要的設(shè)計(jì)模式,關(guān)于這個(gè)接口將在后面介紹。
Tomcat 中 Service 接口的標(biāo)準(zhǔn)實(shí)現(xiàn)類(lèi)是 StandardService 它不僅實(shí)現(xiàn)了 Service 接口,同時(shí)還實(shí)現(xiàn)了 Lifecycle 接口,這樣它就可以控制它下面的組件的生命周期了。StandardService 類(lèi)結(jié)構(gòu)圖如下:
圖 3. StandardService 的類(lèi)結(jié)構(gòu)圖
從上圖中可以看出除了 Service 接口的方法的實(shí)現(xiàn)以及控制組件生命周期的 Lifecycle 接口的實(shí)現(xiàn),還有幾個(gè)方法是用于在事件監(jiān)聽(tīng)的方法的實(shí)現(xiàn),不僅是這個(gè) Service 組件,Tomcat 中其它組件也同樣有這幾個(gè)方法,這也是一個(gè)典型的設(shè)計(jì)模式,將在后面介紹。
下面看一下 StandardService 中主要的幾個(gè)方法實(shí)現(xiàn)的代碼,下面是 setContainer 和 addConnector 方法的源碼:
清單 1. StandardService.SetContainer
這段代碼很簡(jiǎn)單,其實(shí)就是先判斷當(dāng)前的這個(gè) Service 有沒(méi)有已經(jīng)關(guān)聯(lián)了 Container,如果已經(jīng)關(guān)聯(lián)了,那么去掉這個(gè)關(guān)聯(lián)關(guān)系:oldContainer.setService(null)。如果這個(gè) oldContainer 已經(jīng)被啟動(dòng)了,結(jié)束它的生命周期。然后再替換新的關(guān)聯(lián)、再初始化并開(kāi)始這個(gè)新的 Container 的生命周期。最后將這個(gè)過(guò)程通知感興趣的事件監(jiān)聽(tīng)程序。這里值得注意的地方就是,修改 Container 時(shí)要將新的 Container 關(guān)聯(lián)到每個(gè) Connector,還好 Container 和 Connector 沒(méi)有雙向關(guān)聯(lián),不然這個(gè)關(guān)聯(lián)關(guān)系將會(huì)很難維護(hù)。
清單 2. StandardService.addConnector
上面是 addConnector 方法,這個(gè)方法也很簡(jiǎn)單,首先是設(shè)置關(guān)聯(lián)關(guān)系,然后是初始化工作,開(kāi)始新的生命周期。這里值得一提的是,注意 Connector 用的是數(shù)組而不是 List 集合,這個(gè)從性能角度考慮可以理解,有趣的是這里用了數(shù)組但是并沒(méi)有向我們平常那樣,一開(kāi)始就分配一個(gè)固定大小的數(shù)組,它這里的實(shí)現(xiàn)機(jī)制是:重新創(chuàng)建一個(gè)當(dāng)前大小的數(shù)組對(duì)象,然后將原來(lái)的數(shù)組對(duì)象 copy 到新的數(shù)組中,這種方式實(shí)現(xiàn)了類(lèi)似的動(dòng)態(tài)數(shù)組的功能,這種實(shí)現(xiàn)方式,值得我們以后拿來(lái)借鑒。
最新的 Tomcat6 中 StandardService 也基本沒(méi)有變化,但是從 Tomcat5 開(kāi)始 Service、Server 和容器類(lèi)都繼承了 MBeanRegistration 接口,Mbeans 的管理更加合理。
1.2以 Server 為"居"
前面說(shuō)男女因?yàn)?Service 而成為家庭男女,有了能夠組成一個(gè)家庭的基本條件,但是它們還要有個(gè)實(shí)體的家,這是它們?cè)谏鐣?huì)上生存之本,有了家它們就可以安心的為人民服務(wù)了,一起為社會(huì)創(chuàng)造財(cái)富。這個(gè)實(shí)體之家,就由Server來(lái)提供。
Server 要完成的任務(wù)很簡(jiǎn)單,就是要能夠提供一個(gè)接口讓其它程序能夠訪問(wèn)到這個(gè) Service 集合、同時(shí)要維護(hù)它所包含的所有 Service 的生命周期,包括如何初始化、如何結(jié)束服務(wù)、如何找到別人要訪問(wèn)的 Service。還有其它的一些次要的任務(wù),如您住在這個(gè)地方要向當(dāng)?shù)卣サ怯洶?、可能還有要配合當(dāng)?shù)毓矙C(jī)關(guān)日常的安全檢查什么的。
Server 的類(lèi)結(jié)構(gòu)圖如下:
圖 4. Server 的類(lèi)結(jié)構(gòu)圖
它的標(biāo)準(zhǔn)實(shí)現(xiàn)類(lèi) StandardServer 實(shí)現(xiàn)了上面這些方法,同時(shí)也實(shí)現(xiàn)了 Lifecycle、MbeanRegistration 兩個(gè)接口的所有方法,下面主要看一下 StandardServer 重要的一個(gè)方法 addService 的實(shí)現(xiàn):
清單 3. StandardServer.addService
從上面第一句就知道了 Service 和 Server 是相互關(guān)聯(lián)的,Server 也是和 Service 管理 Connector 一樣管理它,也是將 Service 放在一個(gè)數(shù)組中,后面部分的代碼也是管理這個(gè)新加進(jìn)來(lái)的 Service 的生命周期。Tomcat6 中也是沒(méi)有什么變化的。
1.3以Lifecycle為"生"
前面一直在說(shuō) Service 和 Server 管理它下面組件的生命周期(Lifecycle),那它們是如何管理的呢?
Tomcat 中組件的生命周期是通過(guò) Lifecycle 接口來(lái)控制的,組件只要繼承這個(gè)接口并實(shí)現(xiàn)其中的方法就可以統(tǒng)一被擁有它的組件控制了,這樣一層一層的直到一個(gè)最高級(jí)的組件就可以控制 Tomcat 中所有組件的生命周期,這個(gè)最高的組件就是 Server,而控制 Server 的是 Startup,也就是您啟動(dòng)和關(guān)閉 Tomcat。
下面是 Lifecycle 接口的類(lèi)結(jié)構(gòu)圖:
圖 5. Lifecycle 類(lèi)結(jié)構(gòu)圖
除了控制生命周期的 Start 和 Stop 方法外還有一個(gè)監(jiān)聽(tīng)機(jī)制,在生命周期開(kāi)始和結(jié)束的時(shí)候做一些額外的操作。這個(gè)機(jī)制在其它的框架中也被使用,如在 Spring 中。關(guān)于這個(gè)設(shè)計(jì)模式會(huì)在后面介紹。
Lifecycle 接口的方法的實(shí)現(xiàn)都在其它組件中,就像前面中說(shuō)的,組件的生命周期由包含它的父組件控制,所以它的 Start 方法自然就是調(diào)用它下面的組件的 Start 方法,Stop 方法也是一樣。如在 Server 中 Start 方法就會(huì)調(diào)用 Service 組件的 Start 方法,Server 的 Start 方法代碼如下:
清單 4. StandardServer.Start
監(jiān)聽(tīng)的代碼會(huì)包圍 Service 組件的啟動(dòng)過(guò)程,就是簡(jiǎn)單的循環(huán)啟動(dòng)所有 Service 組件的 Start 方法,但是所有 Service 必須要實(shí)現(xiàn) Lifecycle 接口,這樣做會(huì)更加靈活。
Server 的 Stop 方法代碼如下:
清單 5. StandardServer.Stop
它所要做的事情也和 Start 方法差不多。
二、Connector 組件
Connector 組件是 Tomcat 中兩個(gè)核心組件之一,它的主要任務(wù)是負(fù)責(zé)接收瀏覽器的發(fā)過(guò)來(lái)的 tcp 連接請(qǐng)求,創(chuàng)建一個(gè) Request 和 Response 對(duì)象分別用于和請(qǐng)求端交換數(shù)據(jù),然后會(huì)產(chǎn)生一個(gè)線程來(lái)處理這個(gè)請(qǐng)求并把產(chǎn)生的 Request 和 Response 對(duì)象傳給處理這個(gè)請(qǐng)求的線程,處理這個(gè)請(qǐng)求的線程就是 Container 組件要做的事了。
2.1 連接處理流程
由于這個(gè)過(guò)程比較復(fù)雜,大體的流程可以用下面的順序圖來(lái)解釋:
圖 6. Connector 處理一次請(qǐng)求順序圖
Tomcat5 中默認(rèn)的 Connector 是 Coyote,這個(gè) Connector 是可以選擇替換的。Connector 最重要的功能就是接收連接請(qǐng)求然后分配線程讓 Container 來(lái)處理這個(gè)請(qǐng)求,所以這必然是多線程的,多線程的處理是 Connector 設(shè)計(jì)的核心。Tomcat5 將這個(gè)過(guò)程更加細(xì)化,它將 Connector 劃分成 Connector、Processor、Protocol, 另外 Coyote 也定 義自己的 Request 和 Response 對(duì)象。
下面主要看一下 Tomcat 中如何處理多線程的連接請(qǐng)求,先看一下 Connector 的主要類(lèi)圖:
圖 7. Connector 的主要類(lèi)圖
看一下 HttpConnector 的 Start 方法:
清單 6. HttpConnector.Start
threadStart() 執(zhí)行就會(huì)進(jìn)入等待請(qǐng)求的狀態(tài),直到一個(gè)新的請(qǐng)求到來(lái)才會(huì)激活它繼續(xù)執(zhí)行,這個(gè)激活是在 HttpProcessor 的 assign 方法中,這個(gè)方法是代碼如下 :
清單 7. HttpProcessor.assign
創(chuàng)建 HttpProcessor 對(duì)象是會(huì)把 available 設(shè)為 false,所以當(dāng)請(qǐng)求到來(lái)時(shí)不會(huì)進(jìn)入 while 循環(huán),將請(qǐng)求的 socket 賦給當(dāng)期處理的 socket,并將 available 設(shè)為 true,當(dāng) available 設(shè)為 true 是 HttpProcessor 的 run 方法將被激活,接下去將會(huì)處理這次請(qǐng)求。Run 方法代碼如下:
清單 8. HttpProcessor.Run
解析 socket 的過(guò)程在 process 方法中,process 方法的代碼片段如下:
清單 9. HttpProcessor.process
當(dāng) Connector 將 socket 連接封裝成 request 和 response 對(duì)象后接下來(lái)的事情就交給 Container 來(lái)處理了。
三、Servlet 容器"Container"
Container 是容器的父接口,所有子容器都必須實(shí)現(xiàn)這個(gè)接口,Container 容器的設(shè)計(jì)用的是典型的責(zé)任鏈的設(shè)計(jì)模式,它有四個(gè)子容器組件構(gòu)成,分別是:Engine、Host、Context、Wrapper,這四個(gè)組件不是平行的,而是父子關(guān)系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一個(gè) Servlet class 對(duì)應(yīng)一個(gè) Wrapper,如果有多個(gè) Servlet 就可以定義多個(gè) Wrapper,如果有多個(gè) Wrapper 就要定義一個(gè)更高的 Container 了,如 Context,Context 通常就是對(duì)應(yīng)下面這個(gè)配置:
清單 10. Server.xml
3.1 容器的總體設(shè)計(jì)
Context 還可以定義在父容器 Host 中,Host 不是必須的,但是要運(yùn)行 war 程序,就必須要 Host,因?yàn)?war 中必有 web.xml 文件,這個(gè)文件的解析就需要 Host 了,如果要有多個(gè) Host 就要定義一個(gè) top 容器 Engine 了。而 Engine 沒(méi)有父容器了,一個(gè) Engine 代表一個(gè)完整的 Servlet 引擎。
那么這些容器是如何協(xié)同工作的呢?先看一下它們之間的關(guān)系圖:
圖 8. 四個(gè)容器的關(guān)系圖
當(dāng) Connector 接受到一個(gè)連接請(qǐng)求時(shí),將請(qǐng)求交給 Container,Container 是如何處理這個(gè)請(qǐng)求的?這四個(gè)組件是怎么分工的,怎么把請(qǐng)求傳給特定的子容器的呢?又是如何將最終的請(qǐng)求交給 Servlet 處理。下面是這個(gè)過(guò)程的時(shí)序圖:
圖 9. Engine 和 Host 處理請(qǐng)求的時(shí)序圖
這里看到了 Valve 是不是很熟悉,沒(méi)錯(cuò) Valve 的設(shè)計(jì)在其他框架中也有用的,同樣 Pipeline 的原理也基本是相似的,它是一個(gè)管道,Engine 和 Host 都會(huì)執(zhí)行這個(gè) Pipeline,您可以在這個(gè)管道上增加任意的 Valve,Tomcat 會(huì)挨個(gè)執(zhí)行這些 Valve,而且四個(gè)組件都會(huì)有自己的一套 Valve 集合。您怎么才能定義自己的 Valve 呢?在 server.xml 文件中可以添加,如給 Engine 和 Host 增加一個(gè) Valve 如下:
清單 11. Server.xml
StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默認(rèn)的 Valve,它們是最后一個(gè) Valve 負(fù)責(zé)將請(qǐng)求傳給它們的子容器,以繼續(xù)往下執(zhí)行。
前面是 Engine 和 Host 容器的請(qǐng)求過(guò)程,下面看 Context 和 Wrapper 容器時(shí)如何處理請(qǐng)求的。下面是處理請(qǐng)求的時(shí)序圖:
圖 10. Context 和 wrapper 的處理請(qǐng)求時(shí)序圖
從 Tomcat5 開(kāi)始,子容器的路由放在了 request 中,request 中保存了當(dāng)前請(qǐng)求正在處理的 Host、Context 和 wrapper。
3.2 Engine 容器
Engine 容器比較簡(jiǎn)單,它只定義了一些基本的關(guān)聯(lián)關(guān)系,接口類(lèi)圖如下:
圖 11. Engine 接口的類(lèi)結(jié)構(gòu)
它的標(biāo)準(zhǔn)實(shí)現(xiàn)類(lèi)是 StandardEngine,這個(gè)類(lèi)注意一點(diǎn)就是 Engine 沒(méi)有父容器了,如果調(diào)用 setParent 方法時(shí)將會(huì)報(bào)錯(cuò)。添加子容器也只能是 Host 類(lèi)型的,代碼如下:
清單 12. StandardEngine. addChild
它的初始化方法也就是初始化和它相關(guān)聯(lián)的組件,以及一些事件的監(jiān)聽(tīng)。
3.3 Host 容器
Host 是 Engine 的字容器,一個(gè) Host 在 Engine 中代表一個(gè)虛擬主機(jī),這個(gè)虛擬主機(jī)的作用就是運(yùn)行多個(gè)應(yīng)用,它負(fù)責(zé)安裝和展開(kāi)這些應(yīng)用,并且標(biāo)識(shí)這個(gè)應(yīng)用以便能夠區(qū)分它們。它的子容器通常是 Context,它除了關(guān)聯(lián)子容器外,還有就是保存一個(gè)主機(jī)應(yīng)該有的信息。下面是和 Host 相關(guān)的類(lèi)關(guān)聯(lián)圖:
圖 12. Host 相關(guān)的類(lèi)圖
從上圖中可以看出除了所有容器都繼承的 ContainerBase 外,StandardHost 還實(shí)現(xiàn)了 Deployer 接口,上圖清楚的列出了這個(gè)接口的主要方法,這些方法都是安裝、展開(kāi)、啟動(dòng)和結(jié)束每個(gè) web application。Deployer 接口的實(shí)現(xiàn)是 StandardHostDeployer,這個(gè)類(lèi)實(shí)現(xiàn)了的最要的幾個(gè)方法,Host 可以調(diào)用這些方法完成應(yīng)用的部署等。
3.4 Context 容器
Context 代表 Servlet 的 Context,它具備了 Servlet 運(yùn)行的基本環(huán)境,理論上只要有 Context 就能運(yùn)行 Servlet 了。簡(jiǎn)單的 Tomcat 可以沒(méi)有 Engine 和 Host。
Context 最重要的功能就是管理它里面的 Servlet 實(shí)例,Servlet 實(shí)例在 Context 中是以 Wrapper 出現(xiàn)的,還有一點(diǎn)就是 Context 如何才能找到正確的 Servlet 來(lái)執(zhí)行它呢? Tomcat5 以前是通過(guò)一個(gè) Mapper 類(lèi)來(lái)管理的,Tomcat5 以后這個(gè)功能被移到了 request 中,在前面的時(shí)序圖中就可以發(fā)現(xiàn)獲取子容器都是通過(guò) request 來(lái)分配的。
Context 準(zhǔn)備 Servlet 的運(yùn)行環(huán)境是在 Start 方法開(kāi)始的,這個(gè)方法的代碼片段如下:
清單 13. StandardContext.start
它主要是設(shè)置各種資源屬性和管理組件,還有非常重要的就是啟動(dòng)子容器和 Pipeline。我們知道 Context 的配置文件中有個(gè) reloadable 屬性,如下面配置:
清單 14. Server.xml
當(dāng)這個(gè) reloadable 設(shè)為 true 時(shí),war 被修改后 Tomcat 會(huì)自動(dòng)的重新加載這個(gè)應(yīng)用。如何做到這點(diǎn)的呢 ? 這個(gè)功能是在 StandardContext 的 backgroundProcess 方法中實(shí)現(xiàn)的,這個(gè)方法的代碼如下:
清單 15. StandardContext. backgroundProcess
它會(huì)調(diào)用 reload 方法,而 reload 方法會(huì)先調(diào)用 stop 方法然后再調(diào)用 Start 方法,完成 Context 的一次重新加載??梢钥闯鰣?zhí)行 reload 方法的條件是 reloadable 為 true 和應(yīng)用被修改,那么這個(gè) backgroundProcess 方法是怎么被調(diào)用的呢?
這個(gè)方法是在 ContainerBase 類(lèi)中定義的內(nèi)部類(lèi) ContainerBackgroundProcessor 被周期調(diào)用的,這個(gè)類(lèi)是運(yùn)行在一個(gè)后臺(tái)線程中,它會(huì)周期的執(zhí)行 run 方法,它的 run 方法會(huì)周期調(diào)用所有容器的 backgroundProcess 方法,因?yàn)樗腥萜鞫紩?huì)繼承 ContainerBase 類(lèi),所以所有容器都能夠在 backgroundProcess 方法中定義周期執(zhí)行的事件。
3.5 Wrapper 容器
Wrapper 代表一個(gè) Servlet,它負(fù)責(zé)管理一個(gè) Servlet,包括的 Servlet 的裝載、初始化、執(zhí)行以及資源回收。Wrapper 是最底層的容器,它沒(méi)有子容器了,所以調(diào)用它的 addChild 將會(huì)報(bào)錯(cuò)。Wrapper 的實(shí)現(xiàn)類(lèi)是 StandardWrapper,StandardWrapper 還實(shí)現(xiàn)了擁有一個(gè) Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 將直接和 Servlet 的各種信息打交道。
下面看一下非常重要的一個(gè)方法 loadServlet,代碼片段如下:
清單 16. StandardWrapper.loadServlet
它基本上描述了對(duì) Servlet 的操作,當(dāng)裝載了 Servlet 后就會(huì)調(diào)用 Servlet 的 init 方法,同時(shí)會(huì)傳一個(gè) StandardWrapperFacade 對(duì)象給 Servlet,這個(gè)對(duì)象包裝了 StandardWrapper,ServletConfig 與它們的關(guān)系圖如下:
圖 13. ServletConfig 與 StandardWrapperFacade、StandardWrapper 的關(guān)系
Servlet 可以獲得的信息都在 StandardWrapperFacade 封裝,這些信息又是在 StandardWrapper 對(duì)象中拿到的。所以 Servlet 可以通過(guò) ServletConfig 拿到有限的容器的信息。當(dāng) Servlet 被初始化完成后,就等著 StandardWrapperValve 去調(diào)用它的 service 方法了,調(diào)用 service 方法之前要調(diào)用 Servlet 所有的 filter。
四、Tomcat 中其它組件
Tomcat 還有其它重要的組件,如安全組件 security、logger 日志組件、session、mbeans、naming 等其它組件。這些組件共同為 Connector 和 Container 提供必要的服務(wù)。
分享到微信 ×
打開(kāi)微信,點(diǎn)擊底部的“發(fā)現(xiàn)”,
使用“掃一掃”即可將網(wǎng)頁(yè)分享至朋友圈。