- verview
- Connector Init and Start
- Request Process
- Acceptor
- Poller
- Worker
- Container
- Reference
建議結(jié)合《談?wù)?Tomcat 架構(gòu)及啟動(dòng)過(guò)程[含部署]》一起看!
談?wù)?Tomcat 架構(gòu)及啟動(dòng)過(guò)程[含部署]
http://www.importnew.com/27724.html
很多東西在時(shí)序圖中體現(xiàn)的已經(jīng)非常清楚了,沒(méi)有必要再一步一步的作介紹,所以本文以圖為主,然后對(duì)部分內(nèi)容加以簡(jiǎn)單解釋。
繪制圖形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension
本文對(duì) Tomcat 的介紹以 Tomcat-9.0.0.M22 為標(biāo)準(zhǔn)。
Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未發(fā)布,它實(shí)現(xiàn)了 Servlet4.0 及 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,詳情請(qǐng)查閱 Tomcat-9.0-doc。
https://tomcat.apache.org/tomcat-9.0-doc/index.html
Overview
Connector 啟動(dòng)以后會(huì)啟動(dòng)一組線程用于不同階段的請(qǐng)求處理過(guò)程。
- Acceptor 線程組。用于接受新連接,并將新連接封裝一下,選擇一個(gè) Poller 將新連接添加到 Poller 的事件隊(duì)列中。
- Poller 線程組。用于監(jiān)聽(tīng) Socket 事件,當(dāng) Socket 可讀或可寫(xiě)等等時(shí),將 Socket 封裝一下添加到 worker 線程池的任務(wù)隊(duì)列中。
- worker 線程組。用于對(duì)請(qǐng)求進(jìn)行處理,包括分析請(qǐng)求報(bào)文并創(chuàng)建 Request 對(duì)象,調(diào)用容器的 pipeline 進(jìn)行處理。
Acceptor、Poller、worker 所在的 ThreadPoolExecutor 都維護(hù)在 NioEndpoint 中。
Connector Init and Start
- initServerSocket(),通過(guò) ServerSocketChannel.open() 打開(kāi)一個(gè) ServerSocket,默認(rèn)綁定到 8080 端口,默認(rèn)的連接等待隊(duì)列長(zhǎng)度是 100, 當(dāng)超過(guò) 100 個(gè)時(shí)會(huì)拒絕服務(wù)。我們可以通過(guò)配置 conf/server.xml 中 Connector 的 acceptCount 屬性對(duì)其進(jìn)行定制。
- createExecutor() 用于創(chuàng)建 Worker 線程池。默認(rèn)會(huì)啟動(dòng) 10 個(gè) Worker 線程,Tomcat 處理請(qǐng)求過(guò)程中,Woker 最多不超過(guò) 200 個(gè)。我們可以通過(guò)配置 conf/server.xml 中 Connector 的 minSpareThreads 和 maxThreads 對(duì)這兩個(gè)屬性進(jìn)行定制。
- Pollor 用于檢測(cè)已就緒的 Socket。默認(rèn)最多不超過(guò) 2 個(gè),Math.min(2,Runtime.getRuntime().availableProcessors());。我們可以通過(guò)配置 pollerThreadCount 來(lái)定制。
- Acceptor 用于接受新連接。默認(rèn)是 1 個(gè)。我們可以通過(guò)配置 acceptorThreadCount 對(duì)其進(jìn)行定制。
Request Process
Acceptor
- Acceptor 在啟動(dòng)后會(huì)阻塞在 ServerSocketChannel.accept(); 方法處,當(dāng)有新連接到達(dá)時(shí),該方法返回一個(gè) SocketChannel。
- 配置完 Socket 以后將 Socket 封裝到 NioChannel 中,并注冊(cè)到 Poller,值的一提的是,我們一開(kāi)始就啟動(dòng)了多個(gè) Poller 線程,注冊(cè)的時(shí)候,連接是公平的分配到每個(gè) Poller 的。NioEndpoint 維護(hù)了一個(gè) Poller 數(shù)組,當(dāng)一個(gè)連接分配給 pollers[index] 時(shí),下一個(gè)連接就會(huì)分配給 pollers[(index+1)%pollers.length].
- addEvent() 方法會(huì)將 Socket 添加到該 Poller 的 PollerEvent 隊(duì)列中。到此 Acceptor 的任務(wù)就完成了。
Poller
- selector.select(1000)。當(dāng) Poller 啟動(dòng)后因?yàn)?selector 中并沒(méi)有已注冊(cè)的 Channel,所以當(dāng)執(zhí)行到該方法時(shí)只能阻塞。所有的 Poller 共用一個(gè) Selector,其實(shí)現(xiàn)類是 sun.nio.ch.EPollSelectorImpl
- events() 方法會(huì)將通過(guò) addEvent() 方法添加到事件隊(duì)列中的 Socket 注冊(cè)到 EPollSelectorImpl,當(dāng) Socket 可讀時(shí),Poller 才對(duì)其進(jìn)行處理
- createSocketProcessor() 方法將 Socket 封裝到 SocketProcessor 中,SocketProcessor 實(shí)現(xiàn)了 Runnable 接口。worker 線程通過(guò)調(diào)用其 run() 方法來(lái)對(duì) Socket 進(jìn)行處理。
- execute(SocketProcessor) 方法將 SocketProcessor 提交到線程池,放入線程池的 workQueue 中。workQueue 是 BlockingQueue 的實(shí)例。到此 Poller 的任務(wù)就完成了。
Worker
- worker 線程被創(chuàng)建以后就執(zhí)行 ThreadPoolExecutor 的 runWorker() 方法,試圖從 workQueue 中取待處理任務(wù),但是一開(kāi)始 workQueue 是空的,所以 worker 線程會(huì)阻塞在 workQueue.take() 方法。
- 當(dāng)新任務(wù)添加到 workQueue后,workQueue.take() 方法會(huì)返回一個(gè) Runnable,通常是 SocketProcessor,然后 worker 線程調(diào)用 SocketProcessor 的 run() 方法對(duì) Socket 進(jìn)行處理。
- createProcessor() 會(huì)創(chuàng)建一個(gè) Http11Processor, 它用來(lái)解析 Socket,將 Socket 中的內(nèi)容封裝到 Request 中。注意這個(gè) Request 是臨時(shí)使用的一個(gè)類,它的全類名是 org.apache.coyote.Request,
- postParseRequest() 方法封裝一下 Request,并處理一下映射關(guān)系(從 URL 映射到相應(yīng)的 Host、Context、Wrapper)。
- CoyoteAdapter 將 Rquest 提交給 Container 處理之前,并將 org.apache.coyote.Request 封裝到 org.apache.catalina.connector.Request,傳遞給 Container 處理的 Request 是 org.apache.catalina.connector.Request。
- connector.getService().getMapper().map(),用來(lái)在 Mapper 中查詢 URL 的映射關(guān)系。映射關(guān)系會(huì)保留到 org.apache.catalina.connector.Request 中,Container 處理階段 request.getHost() 是使用的就是這個(gè)階段查詢到的映射主機(jī),以此類推 request.getContext()、request.getWrapper() 都是。
- connector.getService().getContainer().getPipeline().getFirst().invoke() 會(huì)將請(qǐng)求傳遞到 Container 處理,當(dāng)然了 Container 處理也是在 Worker 線程中執(zhí)行的,但是這是一個(gè)相對(duì)獨(dú)立的模塊,所以單獨(dú)分出來(lái)一節(jié)。
Container
- 需要注意的是,基本上每一個(gè)容器的 StandardPipeline 上都會(huì)有多個(gè)已注冊(cè)的 Valve,我們只關(guān)注每個(gè)容器的 Basic Valve。其他 Valve 都是在 Basic Valve 前執(zhí)行。
- request.getHost().getPipeline().getFirst().invoke() 先獲取對(duì)應(yīng)的 StandardHost,并執(zhí)行其 pipeline。
- request.getContext().getPipeline().getFirst().invoke() 先獲取對(duì)應(yīng)的 StandardContext,并執(zhí)行其 pipeline。
- request.getWrapper().getPipeline().getFirst().invoke() 先獲取對(duì)應(yīng)的 StandardWrapper,并執(zhí)行其 pipeline。
- 最值得說(shuō)的就是 StandardWrapper 的 Basic Valve,StandardWrapperValve
- allocate() 用來(lái)加載并初始化 Servlet,值的一提的是 Servlet 并不都是單例的,當(dāng) Servlet 實(shí)現(xiàn)了 SingleThreadModel 接口后,StandardWrapper 會(huì)維護(hù)一組 Servlet 實(shí)例,這是享元模式。當(dāng)然了 SingleThreadModel在 Servlet 2.4 以后就棄用了。
- createFilterChain() 方法會(huì)從 StandardContext 中獲取到所有的過(guò)濾器,然后將匹配 Request URL 的所有過(guò)濾器挑選出來(lái)添加到 filterChain 中。
- doFilter() 執(zhí)行過(guò)濾鏈,當(dāng)所有的過(guò)濾器都執(zhí)行完畢后調(diào)用 Servlet 的 service() 方法。
Reference
- 《How Tomcat works》
- https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X
- 《Tomcat 架構(gòu)解析》– 劉光瑞
- http://product.dangdang.com/25084132.html
- Tomcat-9.0-doc
- https://tomcat.apache.org/tomcat-9.0-doc/index.html
- apache-tomcat-9.0.0.M22-src
- http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/
- tomcat架構(gòu)分析 (connector NIO 實(shí)現(xiàn))
- http://gearever.iteye.com/blog/1844203
來(lái)源:http://rrd.me/ehmDB
:-D 搜索微信號(hào)(ID:芋道源碼),可以獲得各種 Java 源碼解析、原理講解、面試題、學(xué)習(xí)指南。
:-D 并且,回復(fù)【書(shū)籍】后,可以領(lǐng)取筆者推薦的各種 Java 從入門到架構(gòu)的 100 本書(shū)籍。
:-D 并且,回復(fù)【技術(shù)群】后,可以加入專門討論 Java、后端、架構(gòu)的技術(shù)群。
源:Rainstorm ,github.com/c-rainstorm/blog/blob/master/tomcat/談?wù)?20Tomcat%20架構(gòu)及啟動(dòng)過(guò)程%5B含部署%5D.md
這個(gè)題目命的其實(shí)是很大的,寫(xiě)的時(shí)候還是很忐忑的,但我盡可能把這個(gè)過(guò)程描述清楚。因?yàn)檫@是讀過(guò)源碼以后寫(xiě)的總結(jié),在寫(xiě)的過(guò)程中可能會(huì)忽略一些前提條件,如果有哪些比較突兀就出現(xiàn),或不好理解的地方可以給我提 Issue,我會(huì)盡快補(bǔ)充修訂相關(guān)內(nèi)容。
很多東西在時(shí)序圖中體現(xiàn)的已經(jīng)非常清楚了,沒(méi)有必要再一步一步的作介紹,所以本文以圖為主,然后對(duì)部分內(nèi)容加以簡(jiǎn)單解釋。
繪制圖形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension
圖形 PlantUML 源文件:
- tomcat-architecture.pu
- tomcat-init.pu
- tomcat-start.pu
- tomcat-context-start.pu
- tomcat-background-thread.pu
本文對(duì) Tomcat 的介紹以 Tomcat-9.0.0.M22 為標(biāo)準(zhǔn)。
Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未發(fā)布,它實(shí)現(xiàn)了 Servlet4.0 及 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,詳情請(qǐng)查閱 Tomcat-9.0-doc
Tomcat-9.0-dochttps://tomcat.apache.org/tomcat-9.0-doc/index.html
Overview
- Bootstrap 作為 Tomcat 對(duì)外界的啟動(dòng)類,在 $CATALINA_BASE/bin 目錄下,它通過(guò)反射創(chuàng)建 Catalina 的實(shí)例并對(duì)其進(jìn)行初始化及啟動(dòng)。
- Catalina 解析 $CATALINA_BASE/conf/server.xml 文件并創(chuàng)建 StandardServer、StandardService、StandardEngine、StandardHost 等
- StandardServer 代表的是整個(gè) Servlet 容器,他包含一個(gè)或多個(gè) StandardService
- StandardService 包含一個(gè)或多個(gè) Connector,和一個(gè) Engine,Connector 和 Engine 都是在解析 conf/server.xml 文件時(shí)創(chuàng)建的,Engine 在 Tomcat 的標(biāo)準(zhǔn)實(shí)現(xiàn)是 StandardEngine
- MapperListener 實(shí)現(xiàn)了 LifecycleListener 和 ContainerListener 接口用于監(jiān)聽(tīng)容器事件和生命周期事件。該監(jiān)聽(tīng)器實(shí)例監(jiān)聽(tīng)所有的容器,包括 StandardEngine、StandardHost、StandardContext、StandardWrapper,當(dāng)容器有變動(dòng)時(shí),注冊(cè)容器到 Mapper。
- Mapper 維護(hù)了 URL 到容器的映射關(guān)系。當(dāng)請(qǐng)求到來(lái)時(shí)會(huì)根據(jù) Mapper 中的映射信息決定將請(qǐng)求映射到哪一個(gè) Host、Context、Wrapper。
- Http11NioProtocol 用于處理 HTTP/1.1 的請(qǐng)求
- NioEndpoint 是連接的端點(diǎn),在請(qǐng)求處理流程中該類是核心類,會(huì)重點(diǎn)介紹。
- CoyoteAdapter 用于將請(qǐng)求從 Connctor 交給 Container 處理。使 Connctor 和 Container 解耦。
- StandardEngine 代表的是 Servlet 引擎,用于處理 Connector 接受的 Request。包含一個(gè)或多個(gè) Host(虛擬主機(jī)), Host 的標(biāo)準(zhǔn)實(shí)現(xiàn)是 StandardHost。
- StandardHost 代表的是虛擬主機(jī),用于部署該虛擬主機(jī)上的應(yīng)用程序。通常包含多個(gè) Context (Context 在 Tomcat 中代表應(yīng)用程序)。Context 在 Tomcat 中的標(biāo)準(zhǔn)實(shí)現(xiàn)是 StandardContext。
- StandardContext 代表一個(gè)獨(dú)立的應(yīng)用程序,通常包含多個(gè) Wrapper,一個(gè) Wrapper 容器封裝了一個(gè) Servlet,Wrapper的標(biāo)準(zhǔn)實(shí)現(xiàn)是 StandardWrapper。
- StandardPipeline 組件代表一個(gè)流水線,與 Valve(閥)結(jié)合,用于處理請(qǐng)求。 StandardPipeline 中含有多個(gè) Valve, 當(dāng)需要處理請(qǐng)求時(shí),會(huì)逐一調(diào)用 Valve 的 invoke 方法對(duì) Request 和 Response 進(jìn)行處理。特別的,其中有一個(gè)特殊的 Valve 叫 basicValve,每一個(gè)標(biāo)準(zhǔn)容器都有一個(gè)指定的 BasicValve,他們做的是最核心的工作。
- StandardEngine 的是 StandardEngineValve,他用來(lái)將 Request 映射到指定的 Host;
- StandardHost 的是 StandardHostValve, 他用來(lái)將 Request 映射到指定的 Context;
- StandardContext 的是 StandardContextValve,它用來(lái)將 Request 映射到指定的 Wrapper;
- StandardWrapper 的是 StandardWrapperValve,他用來(lái)加載 Rquest 所指定的 Servlet,并調(diào)用 Servlet 的 Service 方法。
Tomcat init
- 當(dāng)通過(guò) ./startup.sh 腳本或直接通過(guò) java 命令來(lái)啟動(dòng) Bootstrap 時(shí),Tomcat 的啟動(dòng)過(guò)程就正式開(kāi)始了,啟動(dòng)的入口點(diǎn)就是 Bootstrap 類的 main 方法。
- 啟動(dòng)的過(guò)程分為兩步,分別是 init 和 start,本節(jié)主要介紹 init;
- 初始化類加載器。[關(guān)于 Tomcat 類加載機(jī)制,可以參考我之前寫(xiě)的一片文章:談?wù)凧ava類加載機(jī)制]
- 通過(guò)從 CatalinaProperties 類中獲取 common.loader 等屬性,獲得類加載器的掃描倉(cāng)庫(kù)。CatalinaProperties 類在的靜態(tài)塊中調(diào)用了 loadProperties() 方法,從 conf/catalina.properties 文件中加載了屬性.(即在類創(chuàng)建的時(shí)候?qū)傩跃鸵呀?jīng)加載好了)。
- 通過(guò) ClassLoaderFactory 創(chuàng)建 URLClassLoader 的實(shí)例
- 通過(guò)反射創(chuàng)建 Catalina 的實(shí)例并設(shè)置 parentClassLoader
- setAwait(true)。設(shè)置 Catalina 的 await 屬性為 true。在 Start 階段尾部,若該屬性為 true,Tomcat 會(huì)在 main 線程中監(jiān)聽(tīng) SHUTDOWN 命令,默認(rèn)端口是 8005.當(dāng)收到該命令后執(zhí)行 Catalina 的 stop() 方法關(guān)閉 Tomcat 服務(wù)器。
- createStartDigester()。Catalina 的該方法用于創(chuàng)建一個(gè) Digester 實(shí)例,并添加解析 conf/server.xml 的 RuleSet。Digester 原本是 Apache 的一個(gè)開(kāi)源項(xiàng)目,專門解析 XML 文件的,但我看 Tomcat-9.0.0.M22 中直接將這些類整合到 Tomcat 內(nèi)部了,而不是引入 jar 文件。Digester 工具的原理不在本文的介紹范圍,有興趣的話可以參考 The Digester Component – Apache 或 《How Tomcat works》- Digester [推薦] 一章
- parse() 方法就是 Digester 處理 conf/server.xml 創(chuàng)建各個(gè)組件的過(guò)程。值的一提的是這些組件都是使用反射的方式來(lái)創(chuàng)建的。特別的,在創(chuàng)建 Digester 的時(shí)候,添加了一些特別的 rule Set,用于創(chuàng)建一些十分核心的組件,這些組件在 conf/server.xml 中沒(méi)有但是其作用都比較大,這里做下簡(jiǎn)單介紹,當(dāng) Start 時(shí)用到了再詳細(xì)說(shuō)明:
- EngineConfig。LifecycleListener 的實(shí)現(xiàn)類,觸發(fā) Engine 的生命周期事件后調(diào)用,這個(gè)監(jiān)聽(tīng)器沒(méi)有特別大的作用,就是打印一下日志
- HostConfig。LifecycleListener 的實(shí)現(xiàn)類,觸發(fā) Host 的生命周期事件后調(diào)用。這個(gè)監(jiān)聽(tīng)器的作用就是部署應(yīng)用程序,這包括 conf/<Engine>/<Host>/ 目錄下所有的 Context xml 文件 和 webapps 目錄下的應(yīng)用程序,不管是 war 文件還是已解壓的目錄。 另外后臺(tái)進(jìn)程對(duì)應(yīng)用程序的熱部署也是由該監(jiān)聽(tīng)器負(fù)責(zé)的。
- ContextConfig。LifecycleListener 的實(shí)現(xiàn)類,觸發(fā) Context 的生命周期事件時(shí)調(diào)用。這個(gè)監(jiān)聽(tīng)器的作用是配置應(yīng)用程序,它會(huì)讀取并合并 conf/web.xml 和 應(yīng)用程序的 web.xml,分析 /WEB-INF/classes/ 和 /WEB-INF/lib/*.jar中的 Class 文件的注解,將其中所有的 Servlet、ServletMapping、Filter、FilterMapping、Listener 都配置到 StandardContext 中,以備后期使用。當(dāng)然了 web.xml 中還有一些其他的應(yīng)用程序參數(shù),最后都會(huì)一并配置到 StandardContext 中。
- reconfigureStartStopExecutor() 用于重新配置啟動(dòng)和停止子容器的 Executor。默認(rèn)是 1 個(gè)線程。我們可以配置 conf/server.xml 中 Engine 的 startStopThreads,來(lái)指定用于啟動(dòng)和停止子容器的線程數(shù)量,如果配置 0 的話會(huì)使用 Runtime.getRuntime().availableProcessors() 作為線程數(shù),若配置為負(fù)數(shù)的話會(huì)使用 Runtime.getRuntime().availableProcessors() + 配置值,若和小與 1 的話,使用 1 作為線程數(shù)。當(dāng)線程數(shù)是 1 時(shí),使用 InlineExecutorService 它直接使用當(dāng)前線程來(lái)執(zhí)行啟動(dòng)停止操作,否則使用 ThreadPoolExecutor 來(lái)執(zhí)行,其最大線程數(shù)為我們配置的值。
- 需要注意的是 Host 的 init 操作是在 Start 階段來(lái)做的, StardardHost 創(chuàng)建好后其 state 屬性的默認(rèn)值是 LifecycleState.NEW,所以在其調(diào)用 startInternal() 之前會(huì)進(jìn)行一次初始化。
Tomcat Start[Deployment]
- 圖中從 StandardHost Start StandardContext 的這步其實(shí)在真正的執(zhí)行流程中會(huì)直接跳過(guò),因?yàn)?conf/server.xml 文件中并沒(méi)有配置任何的 Context,所以在 findChildren() 查找子容器時(shí)會(huì)返回空數(shù)組,所以之后遍歷子容器來(lái)啟動(dòng)子容器的 for 循環(huán)就直接跳過(guò)了。
- 觸發(fā) Host 的 BEFORE_START_EVENT 生命周期事件,HostConfig 調(diào)用其 beforeStart() 方法創(chuàng)建 $CATALINA_BASE/webapps& $CATALINA_BASE/conf/<Engine>/<Host>/ 目錄。
- 觸發(fā) Host 的 START_EVENT 生命周期事件,HostConfig 調(diào)用其 start() 方法開(kāi)始部署已在 $CATALINA_BASE/webapps & $CATALINA_BASE/conf/<Engine>/<Host>/ 目錄下的應(yīng)用程序。
- 解析 $CATALINA_BASE/conf/<Engine>/<Host>/ 目錄下所有定義 Context 的 XML 文件,并添加到 StandardHost。這些 XML 文件稱為應(yīng)用程序描述符。正因?yàn)槿绱耍覀兛梢耘渲靡粋€(gè)虛擬路徑來(lái)保存應(yīng)用程序中用到的圖片,詳細(xì)的配置過(guò)程請(qǐng)參考 開(kāi)發(fā)環(huán)境配置指南 – 6.3. 配置圖片存放目錄
- 部署 $CATALINA_BASE/webapps 下所有的 WAR 文件,并添加到 StandardHost。
- 部署 $CATALINA_BASE/webapps 下所有已解壓的目錄,并添加到 StandardHost。
特別的,添加到 StandardHost 時(shí),會(huì)直接調(diào)用 StandardContext 的 start() 方法來(lái)啟動(dòng)應(yīng)用程序。啟動(dòng)應(yīng)用程序步驟請(qǐng)看 Context Start 一節(jié)。
- 在 StandardEngine 和 StandardContext 啟動(dòng)時(shí)都會(huì)調(diào)用各自的 threadStart() 方法,該方法會(huì)創(chuàng)建一個(gè)新的后臺(tái)線程來(lái)處理該該容器和子容器及容器內(nèi)各組件的后臺(tái)事件。StandardEngine 會(huì)直接創(chuàng)建一個(gè)后臺(tái)線程,StandardContext 默認(rèn)是不創(chuàng)建的,和 StandardEngine 共用同一個(gè)。后臺(tái)線程處理機(jī)制是周期調(diào)用組件的 backgroundProcess() 方法。詳情請(qǐng)看 Background process 一節(jié)。
- MapperListener
- addListeners(engine) 方法會(huì)將該監(jiān)聽(tīng)器添加到 StandardEngine 和它的所有子容器中
- registerHost() 會(huì)注冊(cè)所有的 Host 和他們的子容器到 Mapper 中,方便后期請(qǐng)求處理時(shí)使用。
- 當(dāng)有新的應(yīng)用(StandardContext)添加進(jìn)來(lái)后,會(huì)觸發(fā) Host 的容器事件,然后通過(guò) MapperListener 將新應(yīng)用的映射注冊(cè)到 Mapper 中。
- Start 工作都做完以后 Catalina 會(huì)創(chuàng)建一個(gè) CatalinaShutdownHook 并注冊(cè)到 JVM。CatalinaShutdownHook 繼承了 Thread,是 Catalina 的內(nèi)部類。其 run 方法中直接調(diào)用了 Catalina 的 stop() 方法來(lái)關(guān)閉整個(gè)服務(wù)器。注冊(cè)該 Thread 到 JVM 的原因是防止用戶非正常終止 Tomcat,比如直接關(guān)閉命令窗口之類的。當(dāng)直接關(guān)閉命令窗口時(shí),操作系統(tǒng)會(huì)向 JVM 發(fā)送一個(gè)終止信號(hào),然后 JVM 在退出前會(huì)逐一啟動(dòng)已注冊(cè)的 ShutdownHook 來(lái)關(guān)閉相應(yīng)資源。
Context Start
- StandRoot 類實(shí)現(xiàn)了 WebResourceRoot 接口,它容納了一個(gè)應(yīng)用程序的所有資源,通俗的來(lái)說(shuō)就是部署到 webapps 目錄下對(duì)應(yīng) Context 的目錄里的所有資源。因?yàn)槲覍?duì) Tomcat 的資源管理部分暫時(shí)不是很感興趣,所以資源管理相關(guān)類只是做了簡(jiǎn)單了解,并沒(méi)有深入研究源代碼。
- resourceStart() 方法會(huì)對(duì) StandardRoot 進(jìn)行初始配置
- postWorkDirectory() 用于創(chuàng)建對(duì)應(yīng)的工作目錄 $CATALINA_BASE/work/<Engine>/<Host>/<Context>, 該目錄用于存放臨時(shí)文件。
- StardardContext 只是一個(gè)容器,而 ApplicationContext 則是一個(gè)應(yīng)用程序真正的運(yùn)行環(huán)境,相關(guān)類及操作會(huì)在請(qǐng)求處理流程看完以后進(jìn)行補(bǔ)充。
- StardardContext 觸發(fā) CONFIGURE_START_EVENT 生命周期事件,ContextConfig 開(kāi)始調(diào)用 configureStart() 對(duì)應(yīng)用程序進(jìn)行配置。
- 這個(gè)過(guò)程會(huì)解析并合并 conf/web.xml & conf/<Engine>/<Host>/web.xml.default & webapps/<Context>/WEB-INF/web.xml 中的配置。
- 配置配置文件中的參數(shù)到 StandardContext, 其中主要的包括 Servlet、Filter、Listener。
- 因?yàn)閺?Servlet3.0 以后是直接支持注解的,所以服務(wù)器必須能夠處理加了注解的類。Tomcat 通過(guò)分析 WEB-INF/classes/ 中的 Class 文件和 WEB-INF/lib/ 下的 jar 包將掃描到的 Servlet、Filter、Listerner 注冊(cè)到 StandardContext。
- setConfigured(true),是非常關(guān)鍵的一個(gè)操作,它標(biāo)識(shí)了 Context 的成功配置,若未設(shè)置該值為 true 的話,Context 會(huì)啟動(dòng)失敗。
Background process
- 后臺(tái)進(jìn)程的作用就是處理一下 Servlet 引擎中的周期性事件,處理周期默認(rèn)是 10s。
- 特別的 StandardHost 的 backgroundProcess() 方法會(huì)觸發(fā) Host 的 PERIODIC_EVENT 生命周期事件。然后 HostConfig 會(huì)調(diào)用其 check() 方法對(duì)已加載并進(jìn)行過(guò)重新部署的應(yīng)用程序進(jìn)行 reload 或?qū)π虏渴鸬膽?yīng)用程序進(jìn)行熱部署。熱部署跟之前介紹的部署步驟一致, reload() 過(guò)程只是簡(jiǎn)單的順序調(diào)用 setPause(true)、stop()、start()、setPause(false),其中 setPause(true) 的作用是暫時(shí)停止接受請(qǐng)求。
How to read excellent open source projects
真正的第一次閱讀開(kāi)源項(xiàng)目源代碼,收獲還是很大的。讓我在架構(gòu)設(shè)計(jì)、面向?qū)ο笏枷搿⒃O(shè)計(jì)模式、Clean Code等等各個(gè)方面都有了進(jìn)步。閱讀優(yōu)秀的開(kāi)源項(xiàng)目其實(shí)是一件很爽的事,因?yàn)闀r(shí)不時(shí)的會(huì)發(fā)現(xiàn)一個(gè)新的設(shè)計(jì)思路,然后不由自主的感嘆一聲居然還可以這樣!當(dāng)然了,讀的時(shí)候還是會(huì)有一些痛點(diǎn)的,比如說(shuō)碰到一個(gè)變量,但是死活就是找不到初始化的位置,有時(shí)通過(guò) Find Usage 工具可以找到,但有些找不到的只能從頭開(kāi)始再過(guò)一邊源碼。有時(shí)碰到一個(gè)設(shè)計(jì)思路死活都想不明白為什么這樣設(shè)計(jì)等等,這種情況就只能通過(guò)分析更高一層的架構(gòu)來(lái)解決了等等。
下面我簡(jiǎn)單分享一下我是如何閱讀開(kāi)源項(xiàng)目源碼的。
- 先找一些介紹該項(xiàng)目架構(gòu)的書(shū)籍來(lái)看,項(xiàng)目架構(gòu)是項(xiàng)目核心中的核心,讀架構(gòu)讀的是高層次的設(shè)計(jì)思路,讀源碼讀的是低層次的實(shí)現(xiàn)細(xì)節(jié)。有了高層次的設(shè)計(jì)思路做指導(dǎo),源碼讀起來(lái)才會(huì)得心應(yīng)手,因?yàn)樽x的時(shí)候心里很清楚現(xiàn)在在讀的源碼在整個(gè)項(xiàng)目架構(gòu)中處于什么位置。我在讀 Tomcat 源碼之前先把 《How Tomcat works》 一書(shū)過(guò)了一邊,然后又看了一下 《Tomcat 架構(gòu)解析》 的第二章,對(duì) Tomcat 的架構(gòu)有了初步了解。(PS:《How Tomcat works》一書(shū)是全英文的,但讀起來(lái)非常流暢,雖然它是基于 Tomcat 4 和 5 的,但 Tomcat 架構(gòu)沒(méi)有非常大的變化,新版的 Tomcat 只是增加了一些組件,如果你要學(xué)習(xí) Tomcat 的話,首推這本書(shū)!)
- 如果實(shí)在找不到講架構(gòu)的書(shū),那就自己動(dòng)手畫(huà)類圖吧!一般來(lái)說(shuō),開(kāi)源項(xiàng)目都是為了提供服務(wù)的,我們把提供服務(wù)的流程作為主線來(lái)分析源代碼,這樣目的性會(huì)更強(qiáng)一些,將該流程中涉及到的類畫(huà)到類圖中,最后得到的類圖就是架構(gòu)!不過(guò)分析之前你要先找到流程的入口點(diǎn),否則分析就無(wú)從開(kāi)始。以 Tomcat 為例,他的主線流程大致可以分為 3 個(gè):?jiǎn)?dòng)、部署、請(qǐng)求處理。他們的入口點(diǎn)就是 Bootstrap 類和 接受請(qǐng)求的 Acceptor 類!
- 有了閱讀思路我們下面來(lái)說(shuō)說(shuō)工具吧。我使用的閱讀工具是 IntelliJ IDEA,一款十分強(qiáng)大的 IDE,可能比較重量級(jí),如果你有其他更加輕量級(jí)的 Linux 平臺(tái)源碼閱讀工具,可以推薦給我~
- Structure 欄目可以自定義列出類中的域、方法,然后還可以按照繼承結(jié)構(gòu)對(duì)域和方法進(jìn)行分組,這樣就可以直接看出來(lái)域和方法是在繼承結(jié)構(gòu)中哪個(gè)類里定義的。當(dāng)你點(diǎn)擊方法和域時(shí),還可以自動(dòng)滾動(dòng)到源代碼等等。
- 在源代碼中 點(diǎn)擊右鍵 -> Diagrams -> show Diagram 可以顯示類的繼承結(jié)構(gòu),圖中包含了該類所有的祖先和所有的接口。在該圖中選擇指定的父類和接口,點(diǎn)擊右鍵 -> show Implementations, IDEA 會(huì)列出接口的實(shí)現(xiàn)類或該類的子類。
- FindUsage、Go To Declaration 等等就不再多說(shuō)了。
- 目前想到的就這么多,如果你發(fā)現(xiàn)了我沒(méi)有提到的功能,歡迎跟我郵件交流~
Reference
- 《How Tomcat works》
- https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X
- 《Tomcat 架構(gòu)解析》– 劉光瑞
- http://product.dangdang.com/25084132.html
- Tomcat-9.0-doc
- https://tomcat.apache.org/tomcat-9.0-doc/index.html
- apache-tomcat-9.0.0.M22-src
- http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/
直都在做App開(kāi)發(fā),但是對(duì)java web方面的了解比較少,最近有時(shí)間,所以自己就尋思動(dòng)手從零開(kāi)始搭建一個(gè)java web項(xiàng)目。該項(xiàng)目主要是給app,提供接口服務(wù),簡(jiǎn)稱項(xiàng)目為‘mserver’。好了,接下來(lái),我們就開(kāi)始簡(jiǎn)單的搭建項(xiàng)目。
一.開(kāi)發(fā)所需工具。(根據(jù)你的電腦以及系統(tǒng)選擇合適的版本下載)
1.JDK 。下載鏈接。
JDK(Java Development Kit) 是 Java 語(yǔ)言的軟件開(kāi)發(fā)工具包(SDK)。JDK是 Java 語(yǔ)言的軟件開(kāi)發(fā)工具包,主要用于移動(dòng)設(shè)備、嵌入式設(shè)備上的java應(yīng)用程序。JDK是整個(gè)java開(kāi)發(fā)的核心,它包含了JAVA的運(yùn)行環(huán)境,JAVA工具和JAVA基礎(chǔ)的類庫(kù)。
2.eclipse開(kāi)發(fā)工具。下載鏈接。
PS:我們?cè)谙螺d軟件的過(guò)程中,可能會(huì)有多個(gè)不同的下載文件,例如,zip、msi等,那么這兩個(gè)有什么區(qū)別嗎?
- ZIP是免安裝的軟件包,MSI是微軟特有壓縮格式,是需要安裝的軟件包
- ZIP Archive: 壓縮版本,需要自己配置
- MSI Installer:安裝版本,安裝過(guò)程中自動(dòng)配置
3.Tomcat服務(wù)器。下載鏈接。
4.Mysq數(shù)據(jù)庫(kù)。下載鏈接 。
如果你不習(xí)慣使用命令去操作數(shù)據(jù)庫(kù),那么推薦你使用SQLyog工具,下載鏈接。
PS:下載安裝好JDK 后,需要配置環(huán)境變量。(以Win7電腦為例,配置環(huán)境變量)
(1).依次點(diǎn)擊,計(jì)算機(jī)→屬性→高級(jí)系統(tǒng)設(shè)置→高級(jí)→環(huán)境變量;
(2).打開(kāi)系統(tǒng)變量,新建JAVA_HOME 變量,變量值填寫(xiě)jdk的安裝目錄(本人是 D:\Java\jdk1.7.0);
(3).在系統(tǒng)變量中找到Path變量,點(diǎn)擊編輯,
在變量值最后輸入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
(注意原來(lái)Path的變量值末尾如果沒(méi)有‘;’,先輸入';'后再輸入上面的代碼);
(4).在系統(tǒng)變量中新建 CLASSPATH 變量
變量值填寫(xiě) .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意最前面有一點(diǎn));
經(jīng)過(guò)以上幾步,環(huán)境變量就配置完成了。但是,我們還得驗(yàn)證一下是否配置成功,運(yùn)行cmd 輸入 java -version,如出現(xiàn)以下截圖顯示的顯示,說(shuō)明配置成功。
二. 創(chuàng)建項(xiàng)目。
1.打開(kāi)eclipse,先配置一下Tomcat服務(wù)器。
(1).打開(kāi)windows-> preferences,找到Server下方的Runtime Environment,單擊右方的Add按鈕:
(2). 選擇已經(jīng)安裝的Tomcat版本,點(diǎn)擊Next,
(3).找到下載安裝后的Tomcat,點(diǎn)擊Finish。
經(jīng)過(guò)以上幾步,就可以看到,紅色框中出現(xiàn)剛才你添加的Tomcat服務(wù)器。
2.創(chuàng)建項(xiàng)目。
(1). 打開(kāi)eclipse,在Workspace空白的地方,右鍵,New,選擇‘Dynamic Web project’,點(diǎn)擊Next,
(2).輸入項(xiàng)目名稱,選擇服務(wù)器,其他的默認(rèn)就可以,點(diǎn)擊Finish就可以了!
稍等一會(huì),你的Eclipse中會(huì)出現(xiàn)兩個(gè)項(xiàng)目,如下圖所示,表示創(chuàng)建項(xiàng)目成功了。
此時(shí),你可以'run'項(xiàng)目,運(yùn)行后,可能會(huì)報(bào)404,這是因?yàn)槟愕膚eb項(xiàng)目底下沒(méi)有可訪問(wèn)的資源。那么就在項(xiàng)目的‘WebContent’目錄底下,創(chuàng)建一個(gè)比較簡(jiǎn)單的jsp文件index.jsp,具體代碼,如下
- <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
- pageEncoding="ISO-8859-1"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
- <title>Insert title here</title>
- </head>
- <body>
- <h1>hello world!</h1>
- <h2>This is my test page!</h2>
- <h3>Welcome to my page!</h3>
- </body>
- </html>
創(chuàng)建成功后,點(diǎn)擊運(yùn)行,然后在瀏覽器中輸入“http://localhost:8080/mServer/index.jsp”,便可以看到,
經(jīng)過(guò)上面兩大步驟,我們的mServer接口項(xiàng)目已經(jīng)創(chuàng)建成功了,我們就可以開(kāi)始進(jìn)行接口開(kāi)發(fā)了!