初探Tomcat的架構(gòu)設(shè)計
來源丨ytao(ytao-blog)

Tomcat 作為 servlet 容器實現(xiàn),它是基于 Java 語言開發(fā)的輕量級應(yīng)用服務(wù)器。因為 Tomcat 作為應(yīng)用服務(wù)器,它有著完全開源,輕量,性能穩(wěn)定,部署成本低等優(yōu)點,所以它成為目前 Java 開發(fā)應(yīng)用部署的首選,幾乎每個Java Web開發(fā)者都有使用過,但是,你對 Tomcat 的整體設(shè)計有進(jìn)行過了解和思考嗎?
本文將基于 Tomcat8 進(jìn)行分析,具體版本為 Tomcat8 當(dāng)前官網(wǎng)最新修改(2019-11-21 09:28)的版本 v8.5.49
Tomcat 的總體結(jié)構(gòu)中有很多模塊,下圖列出我們將要進(jìn)行分析結(jié)構(gòu)中的主要模塊。其中主要分析的是Service,Connector,Engine,Host,Context,Wrapper。為避免圖層看著太亂,下圖中 n代表該組件可允許存在多個。

如上圖所描述的是:Server 是 tomcat 服務(wù)器,在 Server 中可以存在多個服務(wù) Service 。每個服務(wù)中可有多個連接器和一個 Servlet 引擎 Engine,一個 Service 中多個連接器對應(yīng)一個 Engine。每個 Engine 中,可存在多個域名,這里可用虛擬主機(jī)的概念來表示 Host。每個 Host 中可以存在多個應(yīng)用 Context。Server,Service,Connector,Engine,Host,Context,Wrapper 它們之間的關(guān)系,除了Connector和Engine,它們是平行關(guān)系,其它的都是存在包含關(guān)系。同時,它們也都繼承了 Lifecycle 接口,該接口提供的是生命周期的管理,里面包括:初始化(init),啟動(start),停止(stop),銷毀(destroy)。當(dāng)它的父容器啟動時,會調(diào)用它子容器的啟動,停止也是一樣的。

上圖中,還可以看到,Engine,Host,Context,Wrapper 都繼承自 Container。它有個 backgroundProcess()方法,后臺異步處理,所以繼承它后可以方便的創(chuàng)建異步線程。在 Tomcat7 中,有看到 Service 持有的是 Container,而不是 Engine。估計這也是為什么在當(dāng)前版本中添加 Engine 方法名叫 setContainer。
Tomcat 源碼中有提供 org.apache.catalina.Server接口,對應(yīng)的默認(rèn)實現(xiàn)類為 org.apache.catalina.core.StandardServer,接口里面提供有如下圖方法。

上圖中可以知道 Server 做的工作:對 Service,Address,Port,Catalina 以及全局命名資源的管理操作。Server 在進(jìn)行初始化的時候,會加載我們 server.xml 中配置的數(shù)據(jù)。

這里對其中的 Service 操作的 addService向定義的服務(wù)集添加新服務(wù)進(jìn)行分析:
// 保存服務(wù)的服務(wù)集privateService services[] = newService[0];finalPropertyChangeSupport support = newPropertyChangeSupport(this);@Overridepublicvoid addService(Service service) {// 相互關(guān)聯(lián)service.setServer(this);// 利用同步鎖,防止并發(fā)訪問 來源:https://ytao.topsynchronized(servicesLock) {Service results[] = newService[services.length + 1];// copy 舊的服務(wù)到新的數(shù)組中System.arraycopy(services, 0, results, 0, services.length);// 添加新的 serviceresults[services.length] = service;services = results;// 如果當(dāng)前 server 已經(jīng)啟動,那么當(dāng)前添加的 service 就開始啟動if(getState().isAvailable()) {try{service.start();} catch(LifecycleException e) {// Ignore}}// 使用觀察者模式,當(dāng)被監(jiān)聽對象屬性值發(fā)生變化時通知監(jiān)聽器,remove 是也會調(diào)用。support.firePropertyChange("service", null, service);}}
源碼中可以看到,向服務(wù)器中添加服務(wù)后,隨機(jī)會啟動服務(wù),實則也服務(wù)啟動入口。
ServiceService 的主要職責(zé)就是將 Connector 和 Engine 的組裝在一起。兩者分開的目的也就是使請求監(jiān)聽和請求處理進(jìn)行解耦,能擁有更好的擴(kuò)展性。每個 Service 都是相互獨立的,但是共享一個JVM和系統(tǒng)類庫。這里提供了 org.apache.catalina.Service接口和默認(rèn)實現(xiàn)類 org.apache.catalina.coreStandardService。

在實現(xiàn)類 StandardService 中,主要分析 setContainer和 addConnector兩個方法。
privateEngine engine = null;protectedfinalMapperListener mapperListener = newMapperListener(this);@Overridepublicvoid setContainer(Engine engine) {Engine oldEngine = this.engine;// 判斷當(dāng)前 Service 是否有關(guān)聯(lián) Engineif(oldEngine != null) {// 如果當(dāng)前 Service 有關(guān)聯(lián) Engine,就去掉當(dāng)前關(guān)聯(lián)的 EngineoldEngine.setService(null);}// 如果當(dāng)前新的 Engine 不為空,那么 Engine 關(guān)聯(lián)當(dāng)前 Service,這里是個雙向關(guān)聯(lián)this.engine = engine;if(this.engine != null) {this.engine.setService(this);}// 如果當(dāng)前 Service 啟動了,那么就開始啟動當(dāng)前新的 Engineif(getState().isAvailable()) {if(this.engine != null) {try{this.engine.start();} catch(LifecycleException e) {log.error(sm.getString("standardService.engine.startFailed"), e);}}// 重啟 MapperListener ,獲取一個新的 Engine ,一定是當(dāng)前入?yún)⒌?Enginetry{mapperListener.stop();} catch(LifecycleException e) {log.error(sm.getString("standardService.mapperListener.stopFailed"), e);}try{mapperListener.start();} catch(LifecycleException e) {log.error(sm.getString("standardService.mapperListener.startFailed"), e);}// 如果當(dāng)前 Service 之前有 Engine 關(guān)聯(lián),那么停止之前的 Engineif(oldEngine != null) {try{oldEngine.stop();} catch(LifecycleException e) {log.error(sm.getString("standardService.engine.stopFailed"), e);}}}// Report this property change to interested listenerssupport.firePropertyChange("container", oldEngine, this.engine);}/*** 實現(xiàn)方式和 StandardServer#addService 類似,不在細(xì)述* 注意,Connector 這里沒有像 Engine 一樣與 Service 實現(xiàn)雙向關(guān)聯(lián)*/@Overridepublicvoid addConnector(Connector connector) {synchronized(connectorsLock) {connector.setService(this);Connector results[] = newConnector[connectors.length + 1];System.arraycopy(connectors, 0, results, 0, connectors.length);results[connectors.length] = connector;connectors = results;if(getState().isAvailable()) {try{connector.start();} catch(LifecycleException e) {log.error(sm.getString("standardService.connector.startFailed",connector), e);}}// Report this property change to interested listenerssupport.firePropertyChange("connector", null, connector);}}
Connector 主要用于接收請求,然后交給 Engine 處理請求,處理完后再給 Connector 去返回給客戶端。當(dāng)前使用版本支持的協(xié)議有:HTTP,HHTP/2,AJP,NIO,NIO2,APR主要的功能包括:
監(jiān)聽服務(wù)器端口來讀取客戶端的請求。
解析協(xié)議并交給對應(yīng)的容器處理請求。
返回處理后的信息給客戶端
Connector 對應(yīng)服務(wù)器 server.xml 中配置信息的例子:
port="8080"protocol="HTTP/1.1" connectionTimeout="20000"redirectPort="8443"/>
這里通過配置監(jiān)聽的端口號 port,指定處理協(xié)議 protocol,以及重定向地址 redirectPort。協(xié)議處理類型通過實例化連接器時設(shè)置:
publicConnector() {// 無參構(gòu)造,下面 setProtocol 中默認(rèn)使用HTTP/1.1this(null);}publicConnector(String protocol) {// 設(shè)置當(dāng)前連接器協(xié)議處理類型setProtocol(protocol);// 實例化協(xié)議處理器,并保存到當(dāng)前 Connector 中ProtocolHandler p = null;try{Class> clazz = Class.forName(protocolHandlerClassName);p = (ProtocolHandler) clazz.getConstructor().newInstance();} catch(Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);} finally{this.protocolHandler = p;}if(Globals.STRICT_SERVLET_COMPLIANCE) {uriCharset = StandardCharsets.ISO_8859_1;} else{uriCharset = StandardCharsets.UTF_8;}}/*** 這個設(shè)置再 tomcat9 中被移除,改為必配項*/publicvoid setProtocol(String protocol) {boolean aprConnector = AprLifecycleListener.isAprAvailable() &&AprLifecycleListener.getUseAprConnector();// 這里指定了默認(rèn)協(xié)議和 HTTP/1.1 一樣if("HTTP/1.1".equals(protocol) || protocol == null) {if(aprConnector) {setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");} else{setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");}} elseif("AJP/1.3".equals(protocol)) {if(aprConnector) {setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");} else{setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");}} else{// 最后如果不是通過指定 HTTP/1.1,AJP/1.3 類型的協(xié)議,就通過類名實例化一個協(xié)議處理器setProtocolHandlerClassName(protocol);}}
ProtocolHandler 是一個協(xié)議處理器,針對不同的請求,提供不同實現(xiàn)。實現(xiàn)類 AbstractProtocol 在初始化時,會在最后調(diào)用一個抽象類 AbstractEndpoint 初始化來啟動線程來監(jiān)聽服務(wù)器端口,當(dāng)接收到請求后,調(diào)用 Processor 讀取請求,然后交給 Engine 處理請求。
EngineEngine 對應(yīng)的是, org.apache.catalina.Engine接口和 org.apache.catalina.core.StandardEngine默認(rèn)實現(xiàn)類。Engine 的功能也比較簡單,處理容器關(guān)系的關(guān)聯(lián)。

但是實現(xiàn)類中的 addChild()不是指的子 Engine,而是只能是 Host。同時沒有父容器, setParent是不允許操作設(shè)置的。
@Overridepublicvoid addChild(Container child) {// 添加的子容器必須是 Hostif(!(child instanceofHost))thrownewIllegalArgumentException(sm.getString("standardEngine.notHost"));super.addChild(child);}@Overridepublicvoid setParent(Container container) {thrownewIllegalArgumentException(sm.getString("standardEngine.notParent"));}
server.xml 可以配置我們的數(shù)據(jù):
name="Catalina"defaultHost="localhost"jvmRoute="jvm1">
Host 表示一個虛擬主機(jī)。應(yīng)為我們的服務(wù)器可設(shè)置多個域名,比如 demo.ytao.top,dev.ytao.top。那么我們就要設(shè)置兩個不同 Host 來處理不同域名的請求。當(dāng)過來的請求域名為 demo.ytao.top 時,那么它就會去找該域名 Host 下的 Context。所以我們的 server.xml 配置文件也提供該配置:
name="localhost"appBase="webapps" unpackWARs="true"autoDeploy="true">
到 Context 這里來,就擁有 Servlet 的運行環(huán)境,Engine,Host都是主要維護(hù)容器關(guān)系,不具備運行環(huán)境。我們暫且可將 Context 理解為一個應(yīng)用,例如我們在根目錄下有 ytao-demo-1 和 ytao-demo-2 兩個應(yīng)用,那么這里就是有兩個 Context。這里主要介紹的 addChild方法,該添加的子容器是 Wrapper:
@Overridepublicvoid addChild(Container child) {// Global JspServletWrapper oldJspServlet = null;// 這里添加的子容器只能時 Wrapperif(!(child instanceofWrapper)) {thrownewIllegalArgumentException(sm.getString("standardContext.notWrapper"));}// 判斷子容器 Wrapper 是否為 JspServletboolean isJspServlet = "jsp".equals(child.getName());// Allow webapp to override JspServlet inherited from global web.xml.if(isJspServlet) {oldJspServlet = (Wrapper) findChild("jsp");if(oldJspServlet != null) {removeChild(oldJspServlet);}}super.addChild(child);// 將servlet映射添加到Context組件if(isJspServlet && oldJspServlet != null) {/** The webapp-specific JspServlet inherits all the mappings* specified in the global web.xml, and may add additional ones.*/String[] jspMappings = oldJspServlet.findMappings();for(int i=0; jspMappings!=null&& iaddServletMappingDecoded(jspMappings[i], child.getName());}}}
這里也就是每個應(yīng)用中的 Servlet 管理中心。
WrapperWrapper 是一個 Servlet 的管理中心,它擁有 Servlet 的整個生命周期,它是沒有子容器的,因為它自己就是最底層的容器了。這里主要對 Servlet 加載的分析:
publicsynchronizedServlet loadServlet() throwsServletException{// 如果已經(jīng)實例化或者用實例化池,就直接返回if(!singleThreadModel && (instance != null))return instance;PrintStream out = System.out;if(swallowOutput) {SystemLogHandler.startCapture();}Servlet servlet;try{long t1=System.currentTimeMillis();// 如果 servlet 類名為空,直接拋出 Servlet 異常if(servletClass == null) {unavailable(null);thrownewServletException(sm.getString("standardWrapper.notClass", getName()));}// 從 Context 中獲取 ServletInstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();try{servlet = (Servlet) instanceManager.newInstance(servletClass);} catch(ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrownewServletException(sm.getString("standardWrapper.notServlet", servletClass), e);} catch(Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);unavailable(null);// Added extra log statement for Bugzilla 36630:// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630if(log.isDebugEnabled()) {log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);}// Restore the context ClassLoaderthrownewServletException(sm.getString("standardWrapper.instantiate", servletClass), e);}// 加載聲明了 MultipartConfig 注解的信息if(multipartConfigElement == null) {MultipartConfig annotation =servlet.getClass().getAnnotation(MultipartConfig.class);if(annotation != null) {multipartConfigElement =newMultipartConfigElement(annotation);}}// 對 servlet 類型進(jìn)行檢查if(servlet instanceofContainerServlet) {((ContainerServlet) servlet).setWrapper(this);}classLoadTime=(int) (System.currentTimeMillis() -t1);if(servlet instanceofSingleThreadModel) {if(instancePool == null) {instancePool = newStack<>();}singleThreadModel = true;}// 初始化 servletinitServlet(servlet);fireContainerEvent("load", this);loadTime=System.currentTimeMillis() -t1;} finally{if(swallowOutput) {String log = SystemLogHandler.stopCapture();if(log != null&& log.length() > 0) {if(getServletContext() != null) {getServletContext().log(log);} else{out.println(log);}}}}return servlet;}
這里加載 Servlet,如果該 Servlet 沒有被實例化過,那么一定要加載一個。
到目前為止,大致介紹了 Tomcat8 的主要組件,對 Tomcat 的整體架構(gòu)也有個大致了解了,Tomcat 源碼進(jìn)行重構(gòu)后,可讀性確實要好很多,建議大家可以去嘗試分析下,里面的使用的一些設(shè)計模式,我們在實際編碼過程中,還是有一定的借鑒意義。
?推薦↓↓↓?
長
按
關(guān)
注
?【16個技術(shù)公眾號】都在這里!
涵蓋:程序員大咖、源碼共讀、程序員共讀、數(shù)據(jù)結(jié)構(gòu)與算法、黑客技術(shù)和網(wǎng)絡(luò)安全、大數(shù)據(jù)科技、編程前端、Java、Python、Web編程開發(fā)、Android、iOS開發(fā)、Linux、數(shù)據(jù)庫研發(fā)、幽默程序員等。
萬水千山總是情,點個 “在看” 行不行


