微信公衆號「後端進階」,專一後端技術分享:Java、Golang、WEB框架、分佈式中間件、服務治理等等。
老司機傾囊相授,帶你一路進階,來不及解釋了快上車!
Tomcat 是 Java WEB 開發接觸最多的 Servlet 容器,但它不只僅是一個 Servlet 容器,它仍是一個 WEB 應用服務器,在微服務架構體系下,爲了下降部署成本,減小資源的開銷,追求的是輕量化與穩定,而 Tomcat 是一個輕量級應用服務器,天然被不少開發人員所接受。java
Tomcat 裏面藏着不少值得咱們每一個 Java WEB 開發者學習的知識,能夠這麼說,當你弄懂了 Tomcat 的設計原理,Java WEB 開發對你來講已經沒有什麼祕密可言了。本篇文章主要是跟你們聊聊 Tomcat 的內部架構體系,讓你們對 Tomcat 有個總體的認知。web
前面我也說了,Tomcat 的本質其實就是一個 WEB 服務器 + 一個 Servlet 容器,那麼它必然須要處理網絡的鏈接與 Servlet 的管理,所以,Tomcat 設計了兩個核心組件來實現這兩個功能,分別是鏈接器和容器,鏈接器用來處理外部網絡鏈接,容器用來處理內部 Servlet,我用一張圖來表示它們的關係:apache
一個 Tomcat 表明一個 Server 服務器,一個 Server 服務器能夠包含多個 Service 服務,Tomcat 默認的 Service 服務是 Catalina,而一個 Service 服務能夠包含多個鏈接器,由於 Tomcat 支持多種網絡協議,包括 HTTP/1.一、HTTP/二、AJP 等等,一個 Service 服務還會包括一個容器,容器外部會有一層 Engine 引擎所包裹,負責與處理鏈接器的請求與響應,鏈接器與容器之間經過 ServletRequest 和 ServletResponse 對象進行交流。後端
也能夠從 server.xml 的配置結構能夠看出 tomcat 總體的內部結構:設計模式
<Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/> <Engine defaultHost="localhost" name="Catalina"> <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"> <Context docBase="handler-api" path="/handler" reloadable="true" source="org.eclipse.jst.jee.server:handler-api"/> </Host> </Engine> </Service> </Server>
鏈接器負責將各類網絡協議封裝起來,對外部屏蔽了網絡鏈接與 IO 處理的細節,將處理獲得的 Request 對象傳遞給容器處理,Tomcat 將處理請求的細節封裝到 ProtocolHandler,ProtocolHandler 是一個接口類型,經過實現 ProtocolHandler 來實現各類協議的處理,如 Http11AprProtocol:api
ProtocolHandler 採用組件模式的設計,將處理網絡鏈接,字節流封裝成 Request 對象,再將 Request 適配成 Servlet 處理 ServletRequest 對象這幾個動做,用組件封裝起來了,ProtocolHandler 包括了三個組件:Endpoint、Processor、Adapter。tomcat
Endpoint 在 ProtocolHandler 實現類的構造方法中建立,以下:服務器
public Http11AprProtocol() { super(new AprEndpoint()); }
Endpoint 組件用來處理底層的 Socket 網絡鏈接,AprEndpoint 裏面有個叫 SocketProcessor 的內部類,它負責爲 AprEndpoint 將接收到的 Socket 請求轉化成 Request 對象,SocketProcessor 實現了 Runnable 接口,它會有一個專門的線程池來處理,後面我會單獨從源碼的角度分析 Endpoint 組件的設計原理。微信
org.apache.tomcat.util.net.AprEndpoint.SocketProcessor#doRun:網絡
// Process the request from this socket SocketState state = getHandler().process(socketWrapper, event);
process 方法會建立一個 processor 對象,調用它的 process 方法將 Socket 字節流封裝成 Request 對象,在建立 Processor 組件時,會將 Adapter 組件添加到 Processor 組件中:
org.apache.coyote.http11.AbstractHttp11Protocol#createProcessor:
protected Processor createProcessor() { Http11Processor processor = new Http11Processor(); // set Adapter 組件 processor.setAdapter(getAdapter()); return processor; }
而 Adapter 組件在鏈接器初始化時就已經建立好了:
org.apache.catalina.connector.Connector#initInternal:
// Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter);
目前爲止,Tomcat 只有一個 Adapter 實現類,就是 CoyoteAdapter。Adapter 的主要做用是將 Request 對象適配成容器可以識別的 Request 對象,好比 Servlet 容器,它的只能識別 ServletRequest 對象,這時候就須要 Adapter 適配器類做一層適配。
以上鍊接器的各個組件,我用一張圖說明它們直接的關係:
在 Tomcat 中一共設計了 4 種容器,它們分別爲 Engine、Host、Context、Wrapper,它們的關係以下圖所示:
從上圖可看出,各個容器組件之間的關係是由大到小,即父子關係,它們之間關係造成一個樹狀的結構,它們的實現類都實現了 Container 接口,它有以下方法來控制容器組件之間的關係:
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); }
容器組件之間經過以上幾個方法,便可實現它們之間的父子關係,有沒有發現,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 中有不少組件,組件經過實現 Lifecycle 接口,Tomcat 經過事件機制來實現對這些組件生命週期的管理。
Tomcat 的這種容器設計思想,實際上是運用了組合設計模式的思想,組合設計模式最大的優勢是能夠自由添加節點,這樣也就使得 Tomcat 的容器組件很是地容易進行擴展,符合設計模式中的開閉原則。
如今咱們知道了 Tomcat 的容器組件的組合方式,那咱們如今就來想一個問題:
當一個請求過來時,Tomcat 是如何識別請求並將它交給特定 Servlet 來處理呢?
從容器的組合關係能夠看出,它們調用順序一定是:
Engine -> Host -> Context -> Wrapper -> Servlet
那麼 Tomcat 是如何來定位 Servlet 的呢?答案是利用 Mapper 組件來完成定位的工做。
Mapper 最主要的核心功能是保存容器組件之間訪問路徑的映射關係,它是如何作到這點的呢?
咱們不妨先從源碼入手:
org.apache.catalina.core.StandardService:
protected final Mapper mapper = new Mapper(); protected final MapperListener mapperListener = new MapperListener(this);
Service 實現類中,已經初始化了 Mapper 組件以及它的監聽類 MapperListener,這裏先說明一下,在 Tomcat 組件中,標準的實現組件類前綴會有 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 服務啓動的時候,會調用 MapperListener.start() 方法,最終會執行 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); } }
該方法會註冊新的虛擬主機,接着 registerHost() 方法會註冊 context,以此類推,從而將容器組件直接的訪問的路徑都註冊到 Mapper 中。
定位 Servlet 的流程圖: