摘要:本篇文章主要是跟大家聊聊的內(nèi)部架構(gòu)體系,讓大家對有個(gè)整體的認(rèn)知。方法會創(chuàng)建一個(gè)對象,調(diào)用它的方法將字節(jié)流封裝成對象,在創(chuàng)建組件時(shí),會將組件添加到組件中組件而組件在連接器初始化時(shí)就已經(jīng)創(chuàng)建好了目前為止,只有一個(gè)實(shí)現(xiàn)類,就是。
微信公眾號「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。
老司機(jī)傾囊相授,帶你一路進(jìn)階,來不及解釋了快上車!
Tomcat 是 Java WEB 開發(fā)接觸最多的 Servlet 容器,但它不僅僅是一個(gè) Servlet 容器,它還是一個(gè) WEB 應(yīng)用服務(wù)器,在微服務(wù)架構(gòu)體系下,為了降低部署成本,減少資源的開銷,追求的是輕量化與穩(wěn)定,而 Tomcat 是一個(gè)輕量級應(yīng)用服務(wù)器,自然被很多開發(fā)人員所接受。
Tomcat 里面藏著很多值得我們每個(gè) Java WEB 開發(fā)者學(xué)習(xí)的知識,可以這么說,當(dāng)你弄懂了 Tomcat 的設(shè)計(jì)原理,Java WEB 開發(fā)對你來說已經(jīng)沒有什么秘密可言了。本篇文章主要是跟大家聊聊 Tomcat 的內(nèi)部架構(gòu)體系,讓大家對 Tomcat 有個(gè)整體的認(rèn)知。
前面我也說了,Tomcat 的本質(zhì)其實(shí)就是一個(gè) WEB 服務(wù)器 + 一個(gè) Servlet 容器,那么它必然需要處理網(wǎng)絡(luò)的連接與 Servlet 的管理,因此,Tomcat 設(shè)計(jì)了兩個(gè)核心組件來實(shí)現(xiàn)這兩個(gè)功能,分別是連接器和容器,連接器用來處理外部網(wǎng)絡(luò)連接,容器用來處理內(nèi)部 Servlet,我用一張圖來表示它們的關(guān)系:
一個(gè) Tomcat 代表一個(gè) Server 服務(wù)器,一個(gè) Server 服務(wù)器可以包含多個(gè) Service 服務(wù),Tomcat 默認(rèn)的 Service 服務(wù)是 Catalina,而一個(gè) Service 服務(wù)可以包含多個(gè)連接器,因?yàn)?Tomcat 支持多種網(wǎng)絡(luò)協(xié)議,包括 HTTP/1.1、HTTP/2、AJP 等等,一個(gè) Service 服務(wù)還會包括一個(gè)容器,容器外部會有一層 Engine 引擎所包裹,負(fù)責(zé)與處理連接器的請求與響應(yīng),連接器與容器之間通過 ServletRequest 和 ServletResponse 對象進(jìn)行交流。
也可以從 server.xml 的配置結(jié)構(gòu)可以看出 tomcat 整體的內(nèi)部結(jié)構(gòu):
連接器(Connector)
連接器負(fù)責(zé)將各種網(wǎng)絡(luò)協(xié)議封裝起來,對外部屏蔽了網(wǎng)絡(luò)連接與 IO 處理的細(xì)節(jié),將處理得到的 Request 對象傳遞給容器處理,Tomcat 將處理請求的細(xì)節(jié)封裝到 ProtocolHandler,ProtocolHandler 是一個(gè)接口類型,通過實(shí)現(xiàn) ProtocolHandler 來實(shí)現(xiàn)各種協(xié)議的處理,如 Http11AprProtocol:
ProtocolHandler 采用組件模式的設(shè)計(jì),將處理網(wǎng)絡(luò)連接,字節(jié)流封裝成 Request 對象,再將 Request 適配成 Servlet 處理 ServletRequest 對象這幾個(gè)動作,用組件封裝起來了,ProtocolHandler 包括了三個(gè)組件:Endpoint、Processor、Adapter。
Endpoint 在 ProtocolHandler 實(shí)現(xiàn)類的構(gòu)造方法中創(chuàng)建,如下:
public Http11AprProtocol() { super(new AprEndpoint()); }
Endpoint 組件用來處理底層的 Socket 網(wǎng)絡(luò)連接,AprEndpoint 里面有個(gè)叫 SocketProcessor 的內(nèi)部類,它負(fù)責(zé)為 AprEndpoint 將接收到的 Socket 請求轉(zhuǎn)化成 Request 對象,SocketProcessor 實(shí)現(xiàn)了 Runnable 接口,它會有一個(gè)專門的線程池來處理,后面我會多帶帶從源碼的角度分析 Endpoint 組件的設(shè)計(jì)原理。
org.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun:
// Process the request from this socket SocketState state = getHandler().process(socketWrapper, event);
process 方法會創(chuàng)建一個(gè) processor 對象,調(diào)用它的 process 方法將 Socket 字節(jié)流封裝成 Request 對象,在創(chuàng)建 Processor 組件時(shí),會將 Adapter 組件添加到 Processor 組件中:
org.apache.coyote.http11.AbstractHttp11Protocol#createProcessor:
protected Processor createProcessor() { Http11Processor processor = new Http11Processor(); // set Adapter 組件 processor.setAdapter(getAdapter()); return processor; }
而 Adapter 組件在連接器初始化時(shí)就已經(jīng)創(chuàng)建好了:
org.apache.catalina.connector.Connector#initInternal:
// Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter);
目前為止,Tomcat 只有一個(gè) Adapter 實(shí)現(xiàn)類,就是 CoyoteAdapter。Adapter 的主要作用是將 Request 對象適配成容器能夠識別的 Request 對象,比如 Servlet 容器,它的只能識別 ServletRequest 對象,這時(shí)候就需要 Adapter 適配器類作一層適配。
以上連接器的各個(gè)組件,我用一張圖說明它們直接的關(guān)系:
容器(Container)在 Tomcat 中一共設(shè)計(jì)了 4 種容器,它們分別為 Engine、Host、Context、Wrapper,它們的關(guān)系如下圖所示:
Engine:表示一個(gè)虛擬主機(jī)的引擎,一個(gè) Tomcat Server 只有一個(gè) 引擎,連接器所有的請求都交給引擎處理,而引擎則會交給相應(yīng)的虛擬主機(jī)去處理請求;
Host:表示虛擬主機(jī),一個(gè)容器可以有多個(gè)虛擬主機(jī),每個(gè)主機(jī)都有對應(yīng)的域名,在 Tomcat 中,一個(gè) webapps 就代表一個(gè)虛擬主機(jī),當(dāng)然 webapps 可以配置多個(gè);
Context:表示一個(gè)應(yīng)用容器,一個(gè)虛擬主機(jī)可以擁有多個(gè)應(yīng)用,webapps 中每個(gè)目錄都代表一個(gè) Context,每個(gè)應(yīng)用可以配置多個(gè) Servlet。
從上圖可看出,各個(gè)容器組件之間的關(guān)系是由大到小,即父子關(guān)系,它們之間關(guān)系形成一個(gè)樹狀的結(jié)構(gòu),它們的實(shí)現(xiàn)類都實(shí)現(xiàn)了 Container 接口,它有如下方法來控制容器組件之間的關(guān)系:
public interface Container extends Lifecycle { Container getParent(); void setParent(Container container); void addChild(Container child); Container findChild(String name); Container[] findChildren(); void removeChild(Container child); }
容器組件之間通過以上幾個(gè)方法,即可實(shí)現(xiàn)它們之間的父子關(guān)系,有沒有發(fā)現(xiàn),Container 接口還繼承了 Lifecycle 接口,它有如下方法:
public interface Lifecycle { public static final String INIT_EVENT = "init"; public static final String START_EVENT = "start"; public static final String BEFORE_START_EVENT = "before_start"; public static final String AFTER_START_EVENT = "after_start"; public static final String STOP_EVENT = "stop"; public static final String BEFORE_STOP_EVENT = "before_stop"; public static final String AFTER_STOP_EVENT = "after_stop"; public static final String DESTROY_EVENT = "destroy"; public void addLifecycleListener(LifecycleListener listener); public LifecycleListener[] findLifecycleListeners(); public void removeLifecycleListener(LifecycleListener listener); public void start() throws LifecycleException; public void stop() throws LifecycleException; }
Tomcat 中有很多組件,組件通過實(shí)現(xiàn) Lifecycle 接口,Tomcat 通過事件機(jī)制來實(shí)現(xiàn)對這些組件生命周期的管理。
Tomcat 的這種容器設(shè)計(jì)思想,其實(shí)是運(yùn)用了組合設(shè)計(jì)模式的思想,組合設(shè)計(jì)模式最大的優(yōu)點(diǎn)是可以自由添加節(jié)點(diǎn),這樣也就使得 Tomcat 的容器組件非常地容易進(jìn)行擴(kuò)展,符合設(shè)計(jì)模式中的開閉原則。
現(xiàn)在我們知道了 Tomcat 的容器組件的組合方式,那我們現(xiàn)在就來想一個(gè)問題:
當(dāng)一個(gè)請求過來時(shí),Tomcat 是如何識別請求并將它交給特定 Servlet 來處理呢?
從容器的組合關(guān)系可以看出,它們調(diào)用順序必定是:
Engine -> Host -> Context -> Wrapper -> Servlet
那么 Tomcat 是如何來定位 Servlet 的呢?答案是利用 Mapper 組件來完成定位的工作。
Mapper 最主要的核心功能是保存容器組件之間訪問路徑的映射關(guān)系,它是如何做到這點(diǎn)的呢?
我們不妨先從源碼入手:
org.apache.catalina.core.StandardService:
protected final Mapper mapper = new Mapper(); protected final MapperListener mapperListener = new MapperListener(this);
Service 實(shí)現(xiàn)類中,已經(jīng)初始化了 Mapper 組件以及它的監(jiān)聽類 MapperListener,這里先說明一下,在 Tomcat 組件中,標(biāo)準(zhǔn)的實(shí)現(xiàn)組件類前綴會有 Standard,比如:
org.apache.catalina.core.StandardServer org.apache.catalina.core.StandardService org.apache.catalina.core.StandardEngine org.apache.catalina.core.StandardHost org.apache.catalina.core.StandardContext org.apache.catalina.core.StandardWrapperz
在 Service 服務(wù)啟動的時(shí)候,會調(diào)用 MapperListener.start() 方法,最終會執(zhí)行 MapperListener 的 startInternal 方法:
org.apache.catalina.mapper.MapperListener#startInternal:
Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } }
該方法會注冊新的虛擬主機(jī),接著 registerHost() 方法會注冊 context,以此類推,從而將容器組件直接的訪問的路徑都注冊到 Mapper 中。
定位 Servlet 的流程圖:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74716.html
摘要:相信大家都聽說過反向代理,一提到反向代理一定會想到。由于是一款自由的開源的高性能的服務(wù)器和反向代理服務(wù)器這是個(gè)開源的時(shí)代啊是一個(gè)跨平臺服務(wù)器,可以運(yùn)行在等操作系統(tǒng)上。所以反向代理服務(wù)器是引用在服務(wù)端。 本文來自于我的慕課網(wǎng)手記:聊聊 Nginx 的反向代理,轉(zhuǎn)載請保留鏈接 ;) 背景 最近在優(yōu)化服務(wù)基礎(chǔ)設(shè)施這塊,正好有時(shí)間寫一下Nginx的體會。相信大家都聽說過反向代理,一提到反向代理...
摘要:英文全名為,也叫遠(yuǎn)程過程調(diào)用,其實(shí)就是一個(gè)計(jì)算機(jī)通信協(xié)議,它是一種通過網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請求服務(wù)而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議。 Hello,Dubbo 你好,dubbo,初次見面,我想和你交個(gè)朋友。 Dubbo你到底是什么? 先給出一套官方的說法:Apache Dubbo是一款高性能、輕量級基于Java的RPC開源框架。 那么什么是RPC? 文檔地址:http://dubbo.a...
閱讀 1925·2021-11-11 16:55
閱讀 2153·2021-10-08 10:13
閱讀 772·2019-08-30 11:01
閱讀 2192·2019-08-29 13:19
閱讀 3307·2019-08-28 18:18
閱讀 2646·2019-08-26 13:26
閱讀 605·2019-08-26 11:40
閱讀 1898·2019-08-23 17:17