一、背景
Tomcat作爲JavaWeb領域的Web容器,目前在我們淘寶也使用的也非常廣泛,現在基本上所有線上業務系統都是部署在Tomcat上。爲了對平時開發的Web系統有更深入的理解以及出於好奇心對我們寫的Web系統是如何跑在Tomcat上的,於是仔細研究了下Tomcat的源碼。大家都知道Servlet規範是Java領域中爲服務端編程制定的規範,對於我們開發者只是關注了Servlet規範中提供的編程組件(ServletContextListener,Filer,Servlet) 等 ,但是規範中還有一些我們經常使用的接口(ServletContext,ServletRequest,ServletResponse,FilterChain)等都是由Tomcat去實現的,並且我們開發者實現的編程組件只是被Tomcat去回調而已。所以看Tomcat源碼實現也有助於我們更好的理解Servlet規範及系統如何在容器中運行(一些開源的MVC框架如Struts2,Webx,SpringMVC本質無非就是這個),順便整理了一下與大家分享(Tomcat版本7.0.23,JDK版本1.6)。
二、Tomcat源碼目錄結構
三、Tomcat體系結構
仔細查看下圖(網絡上描述Tomcat架構比較清晰的一張圖),不難發現其中的Connecotr組件以及與Container組件是Tomcat的核心。一個Server可以有多個Service,而一個Service可以包含了多個Connector組件和一個Engine容器組件,一個Engine可以由多個虛擬主機Host組成,每一個Host下面又可以由多個Web應用Context構成,每一個的Context下面可以包含多個Wrapper(Servlet的包裝器)組成。
Tomcat將Engine,Host,Context,Wrapper統一抽象成Container。一個抽象的Container模塊可以包含各種服務。例如,Manager管理器(Session管理),Pipeline管道( 維護管道閥門Value )等。Lifecycle接口統一定義了容器的生命週期,通過事件機制實現各個容器間的內部通訊。而容器的核心接口Container的抽象實現中定義了一個Pipeline,一個Manager,一個Realm以及ClassLoader統一了具體容器的實現規範。連接器(Connector)組件的主要任務是爲其所接收到的每一個請求(可以是HTTP協議,也可以AJP協議),委託給具體相關協議的解析類ProtocolHandler,構造出Request 對象和Response 對象。然後將這兩個對象傳送給容器(Container)進行處理。容器(Container)組件收到來自連接器(Connector)的Request 和Response對象後,負責調用Filter,最後調用Servlet的service 方法(進入我們開發的Web系統中)。
四、Tomcat源碼解析
Servlet規範由一組用 Java編程語言編寫的類和接口組成。Servlet規範爲服務端開發人員提供了一個標準的 API以及爲服務器廠商制定了相關實現規範,開發人員只需要關心Servlet規範中的編程組件(如Filter,Servlet等),其他規範接口由第三方服務器廠商(如Tomcat)去實現,三者的關係如下圖,Servlet規範之於Tomcat的關係,也類似於JDBC規範與數據庫驅動的關係,本質就是一套接口和一套實現的關係。對於一個Web服務器主要需要做的事情,個人認爲基本由以下組件組成: [TCP連接管理] --> [請求處理線程池管理] --> [HTTP協議解析封裝] --> [Servlet規範的實現,對編程組件的回調] --> [MVC框架,Web系統業務邏輯]。
Tomcat的源碼及功能點因爲實在過於龐大,下面的源碼解析不可能全部功能模塊都涉及到,接下來我們主要會根據Tomcat的啓動、請求處理、關閉三個流程來梳理Tomcat的具體實現,儘可能把各個模塊的核心組件都展示出來。 基本上每個流程的源碼分析我都畫了時序圖,如果不想看文字的話,可以對着時序圖看(如果想看完整的源代碼分析也會在附件中上傳),最後還會分析下Tomcat的Connector組件及Tomcat運行過程中的線程概況及線程模型及Tomcat的類加載機制及Tomcat所涉及的設計模式。
Servlet規範和Web應用及Web容器間的關系
Tomcat對Servlet規範的實現
1. Tomcat的啓動流程
Tomcat在啓動時的重點功能如下:
· 初始化類加載器:主要初始化Tomcat加載自身類庫的StandardClassLoader。
· 解析配置文件:使用Digester組件解析Tomcat的server.xml,初始化各個組件(包含各個web應用,解析對應的web.xml進行初始化)。
· 初始化Tomcat的各級容器Container,當然最後會初始我們Web應用(我們熟悉的Listener,Filter,Servlet等初始化等在這裏完成)。
· 初始化連接器Connector:初始化配置的Connector,以指定的協議打開端口,等待請求。
不管是是通過腳本bootstrap.sh啓動還是通過Eclipse中啓動,Tomcat的啓動流程是在org.apache.catalina.startup.Bootstrap類的main方法中開始的,啓動時這個類的核心代碼如下所示:
Java代碼
1. public static void main(String args[]) {
2. if (daemon == null) {
3. daemon = new Bootstrap(); //實例化Bootstrap的一個實例
4. try {
5. // 創建StandardClassLoader類加載器,並設置爲main線程的線程上下文類加載器
6. // 通過StandardClassLoader加載類Cataline並創建Catalina對象 ,並保存到Boosstrap對象中
7. daemon.init();
8. } catch (Throwable t) {
9. }
10. }
11. if (command.equals("start")) {
12. daemon.setAwait(true);
13. daemon.load(args); //執行load,加載資源,調用Catalina的load方法,利用Digester讀取及解析server.xml配置文件並且創建StandardServer服務器對象 */
14. daemon.start(); //啓動各個組件,容器開始啓動,調用Catalina的start方法
15. }
16. }
從以上的代碼中,可以看到在Tomcat啓動的時候,執行了三個關鍵方法即init、load、和start。後面的兩個方法都是通過反射調用org.apache.catalina.startup.Catalina的同名方法完成的,所以後面在介紹時將會直接轉到Catalina的同名方法。首先分析一下Bootstrap的init方法,在該方法中將會初始化一些全局的系統屬性、初始化類加載器、通過反射得到Catalina實例,在這裏我們重點看一下初始化類加載器的initClassLoaders()方法:
Java代碼
1. private void initClassLoaders() {
2. try {
3. //創建StandardClassLoader類加載器
4. commonLoader = createClassLoader("common", null);
5. if( commonLoader == null ) {
6. commonLoader=this.getClass().getClassLoader();
7. }
8. catalinaLoader = createClassLoader("server", commonLoader);
9. sharedLoader = createClassLoader("shared", commonLoader);
10. } catch (Throwable t) {
11. System.exit(1);
12. }
13. }
在以上的代碼總,我們可以看到初始化了StandardClassLoader類加載器,這個類加載器詳細的會在後面關於Tomcat類加載器機制中介紹。
然後我們進入Catalina的load方法:
Java代碼
1. public void load() {
2. //初始化Digester組件,定義瞭解析規則
3. Digester digester = createStartDigester();
4. try {
5. inputSource.setByteStream(inputStream);
6. digester.push(this);
7. //通過Digester解析這個文件,在此過程中會初始化各個組件實例及其依賴關係
8. //最後會把server.xml文件中的內容解析到StandardServer中
9. digester.parse(inputSource);
10. inputStream.close();
11. } catch (Exception e) {}
12. if (getServer() instanceof Lifecycle) {
13. try {
14. // 調用Server的initialize方法,初始化各個組件
15. getServer().init();
16. } catch (LifecycleException e) {
17. }
18. }
19. }
在以上的代碼中,關鍵的任務有兩項即使用Digester組件按照給定的規則解析server.xml、調用Server的init方法,而Server的init方法中,會發布事件並調用各個Service的init方法,從而級聯完成各個組件的初始化。每個組件的初始化都是比較有意思的,但是我們限於篇幅先關注Tomcat各級容器的初始化及Connector的初始化,這可能是最值得關注的地方。
首先看下StandardService的start方法(), 核心代碼如下:
1. // StandardService的啓動
2. protected void startInternal() throws LifecycleException {
4. //啓動容器StandardEngine
5. if (container != null) {
6. synchronized (container) {
7. // 啓動StandardEngine容器
8. container.start();
9. }
10. }
12. //兩個connector的啓動,HTTP/1.18080和AJP/1.38009
13. synchronized (connectors) {
14. for (Connector connector: connectors) {
15. try {
16. if (connector.getState() != LifecycleState.FAILED) {
17. //依次啓動Connector[HTTP/1.1-8080]或Connector[AJP/1.3-8009]
18. //這裏會創建請求線程執行器並啓動接收線程Acceptor
19. connector.start();
20. }
21. } catch (Exception e) {
22. }
23. }
24. }
25. }
啓動Tomcat各級容器的會依次先啓動StandardEngine --> StandardHost --> StandardContext(代表一個WebApp應用), 因爲我們比較關心我們的Web應用是哪裏被初始化回調的,所以就重點看下StandardContext的start()方法,核心代碼如下:
Java代碼
1. //啓動WebApp應用
2. @Override
3. protected synchronized void startInternal() throws LifecycleException {
4. boolean ok = true;
6. if (getLoader() == null) {
7. //創建此WebApp應用的WebApp載入器WebAppLoader,這個應用的WebAppClassLoader類加載器全部是由這個載入器創建
8. WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
9. webappLoader.setDelegate(getDelegate());
10. //在這裏開始啓動WebAppLoader加載器,並且創建此WebApp的WebAppClassLoader類加載器,保存到WebAppLoader中,最後會被設置到此Context的InstanceManager中
11. setLoader(webappLoader);
12. }
14. ///線程上下文類加載器切換成當前WebApp的類加載器,從Context的loader中獲取
15. ClassLoader oldCCL = bindThread();
17. try {
18. if (ok) {
19. //觸發Context組件的configure_start事件,通知ContextConfig監聽器
20. //開始解析Web應用WEB-INF/web.xml文件配置到WebXml對象,最後把配置信息全部解析到StandardContext
21. fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
23. if (pipeline instanceof Lifecycle) {
24. //啓動StandardContext的管道Pipeline
25. ((Lifecycle) pipeline).start();
26. }
27. }
28. } finally {
29. //線程上下文類加載器切換成之前的StandardClassLoader
30. unbindThread(oldCCL);
31. }
33. //線程上下文類加載器切換成當前WebApp的類加載器,從Context的loader中獲取
34. oldCCL = bindThread();
36. if (ok ) {
37. if (getInstanceManager() == null) {
38. Map<String, Map<String, String>> injectionMap = buildInjectionMap(
39. getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
40. //創建每個應用StandardContext自己的DefaultInstanceManager實例管理器對象
41. //並且會從WebAppLoader拿到WebAppClassLoader類加載器,之後此WebApp的應用類都由此ClassLoader加載
42. setInstanceManager(new DefaultInstanceManager(context,
43. injectionMap, this, this.getClass().getClassLoader()));
44. }
45. }
47. try {
48. //把StandardContext的上下文參數設置到ServletContext中
49. mergeParameters();
51. if (ok) {
52. //啓動監聽器Listener
53. if (!listenerStart()) {
54. log.error( "Error listenerStart");
55. ok = false;
56. }
57. }
59. try {
60. if ((manager != null) && (manager instanceof Lifecycle)) {
61. //啓動Session管理器StandardManager
62. ((Lifecycle) getManager()).start();
63. }
64. super.threadStart();
65. } catch(Exception e) {
66. ok = false;
67. }
69. if (ok) {
70. //啓動過濾器Filter
71. if (!filterStart()) {
72. log.error("Error filterStart");
73. ok = false;
74. }
75. }
77. if (ok) {
78. //啓動load-on-startup的Servlet
79. loadOnStartup(findChildren());
80. }
82. } finally {
83. // WepApp應用啓動完成,線程上下文類加載器切換成之前的StandardClassLoader
84. unbindThread(oldCCL);
85. }
86. }
Tomcat的各級容器初始化完成後,就開始對Connector的初始化,接着看Connector的initInternal方法,核心代碼如下:
Java代碼
1. public void initInternal() throws LifecycleException{
2. //該協議適配器會完成請求的真正處理
3. adapter = new CoyoteAdapter(this);
4. //對於不同的實現,會有不同的ProtocolHandler實現類, Http11Protocol用來處理HTTP請求
5. protocolHandler.setAdapter(adapter);
6. try {
7. // 初始化Http11Protocol協議
8. protocolHandler.init();
9. } catch (Exception e) {
10. }
11. }
在Http11Protocol的init方法中,核心代碼如下:
Java代碼
1. public void init() throws Exception {
2. endpoint.setName(getName());
3. endpoint.setHandler(cHandler);
4. try {
5. endpoint.init(); //核心代碼就是調用JIoEndpoint的初始化方法
6. } catch (Exception ex) {
7. }
8. }
我們看到最終的初始化方法最終都會調到JIoEndpoint的bind方法,網絡初始化和對請求的最初處理都是通過該類及其內部類完成的,後續的內容會詳細闡述這個JioEndpoint:
Java代碼
1. public void bind() throws Exception {
2. //請求接收線程數,默認爲1
3. if (acceptorThreadCount == 0) {
4. acceptorThreadCount = 1;
5. }
6. if (serverSocket == null) {
7. try {
8. if (address == null) {
9. //創建ServerSocket,綁定指定端口並打開該端口的服務,默認8080端口
10. serverSocket = serverSocketFactory.createSocket(port, backlog);
11. } else {
12. serverSocket = serverSocketFactory.createSocket(port, backlog, address);
13. }
14. } catch (BindException orig) {
15. }
16. }
17. }
在上面的代碼中,我們可以看到此時初始化了一個ServerSocket對象,用來監聽綁定端口的請求。
緊接着我們看JioEndpoint的start()方法,核心代碼如下:
Java代碼
1. public void startInternal() throws Exception {
2. if (!running) {
4. if (getExecutor() == null) {
5. //創建請求處理線程池ThreadPoolExecutor, 請求接收線程啓動前必須把請求處理線程池準備好
6. createExecutor();
7. }
9. initializeConnectionLatch();
11. //創建並啓動Acceptor接收線程
12. startAcceptorThreads();
13. }
14. }
從以上的代碼,可以看到,如果沒有在server.xml中聲明Executor的話,將會使用內部的一個容量爲200的線程池用來後續的請求處理。並且按照參數acceptorThreadCount的設置,初始化線程來接受請求。而Acceptor就是正在接受請求並會分派給請求處理線程池:
Java代碼
1. protected class Acceptor implements Runnable {
2. public void run() {
3. while (running) {
4. // 默認監聽8080端口請求,server.xml中的默認配置
5. Socket socket =serverSocketFactory.acceptSocket(serverSocket);
6. serverSocketFactory.initSocket(socket);
7. // 處理Socket,把客戶端請求Socket交給線程池來處理,當前Acceptor線程繼續返回接收客戶端Socket
8. if (!processSocket(socket)) {
9. //關閉連接
10. try {
11. socket.close();
12. } catch (IOException e) {
13. }
14. }
15. }
16. }
17. }
從這裏我們可以看到,Acceptor已經可以接收Socket請求了,並可以調用processSocket方法來對請求進行處理。至此,Tomcat的組件啓動初始化完成,等待請求的到來。
Tomcat的啓動流程具體序列圖如下:
2. Tomcat一次完整請求的處理流程
Tomcat一次完整請求處理的重點功能如下:
· 接收Socket請求,把請求轉發到線程池,由線程池分配一個線程來處理。
· 獲取Socket數據包之後,解析HTTP協議,翻譯成Tomcat內部的Request和Response對象,再映射相應Container。
· Request和Response對象進入Tomcat中的Container容器的各級Piepline管道去流式執行,最終會流到StandardWrapperValve這個閥門。
· 在 StandardWrapperValve這個閥門中,把當前請求的Filter及Servlet封裝成FilterChain, 最終執行FilterChain, 回調Web應用,完成響應。
JIoEndpoint的Acceptor線程在接收到用戶的請求之後,調用processSocket方法。該方法主要是從Executor請求處理線程池中獲取一個線程,然後啓用一個新線程執行Socket請求,JIoEndpoint的processSocket()方法的核心代碼如下:
Java代碼
1. protected boolean processSocket(Socket socket) {
2. try {
3. //把Socket包裝成SocketWrapper
4. SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
5. //包裝成SocketProcessor交給線程池處理,當前Acceptor線程不處理,以便接收下一個到達的請求
6. getExecutor().execute(new SocketProcessor(wrapper));
7. } catch (RejectedExecutionException x) {
8. return false;
9. }
10. return true;
11. }
接着請求處理線程池會起一個線程來處理請求來執行SocketProcessor,而SocketProcessor的run()方法中會調用Http11ConnectionHandler的process方法,SocketProcessor的run()方法核心代碼如下:
Java代碼
1. // Tomcat線程池中的請求處理線程
2. public void run() {
3. boolean launch = false;
4. synchronized (socket) {
5. try {
6. SocketState state = SocketState.OPEN;
7. if ((state != SocketState.CLOSED)) {
8. if (status == null) {
9. //通過Http11Protocol$Http11ConnectionHandler處理請求,引用外部類對象的成員handler
10. state = handler.process(socket, SocketStatus.OPEN);
11. } else {
12. state = handler.process(socket, status);
13. }
14. }
15. if (state == SocketState.CLOSED) {
16. try {
17. //關閉Socket
18. socket.getSocket().close();
19. } catch (IOException e) {
20. }
21. }
22. }
23. }
24. //清空請求Socket
25. socket = null;
26. }
在Http11ConnectionHandler中會根據當前請求的協議類型去創建相應的協議處理器,我們這裏分析的是HTTP協議,所以會創建Http11Processor去執行process()方法, 拿到Socket數據包後解析生成Tomcat內部的Request對象與Response對象。其中Request對象只是解析Header部分內容,請求參數等做延遲處理,接着就開始調用CoyoteAdapter類進入容器處理, Http11Processor的process方法核心代碼如下:
Java代碼
1. public SocketState process(SocketWrapper<S> socketWrapper)
2. throws IOException {
3. RequestInfo rp = request.getRequestProcessor();
4. //設置SocketWrapper
5. setSocketWrapper(socketWrapper);
7. //獲取Socket中的inputStream設置到inputBuffer,也就是設置到Request中
8. getInputBuffer().init(socketWrapper, endpoint);
9. //獲取Socket中的outputStream設置到outputBuffer,也就是設置到Response中
10. getOutputBuffer().init(socketWrapper, endpoint);
12. while (!error && keepAlive && !comet && !isAsync() &&
13. !endpoint.isPaused()) {
14. try {
15. setRequestLineReadTimeout();
17. //解析HTTP請求的method,requestURI,protocol等
18. if (!getInputBuffer().parseRequestLine(keptAlive)) {
19. if (handleIncompleteRequestLineRead()) {
20. break;
21. }
22. }
24. if (endpoint.isPaused()) {
25. response.setStatus(503);
26. error = true;
27. } else {
28. request.setStartTime(System.currentTimeMillis());
29. keptAlive = true;
31. //解析HTTP請求的報頭headers
32. if (!getInputBuffer().parseHeaders()) {
33. openSocket = true;
34. readComplete = false;
35. break;
36. }
37. }
38. } catch (IOException e) {
39. } catch (Throwable t) {
40. }
42. if (!error) {
43. try {
44. //準備Request,根據已解析的信息做一些過濾
45. prepareRequest();
46. } catch (Throwable t) {
47. ExceptionUtils.handleThrowable(t);
48. }
49. }
50. if (!error) {
51. try {
52. //調用CoyoteAdapter的service方法,傳入org.apache.coyote.Request對象及org.apache.coyote.Response對象
53. adapter.service(request, response);
54. } catch (InterruptedIOException e) {
55. error = true;
56. } 39. } catch (Throwable t) {
40. }
42. if (!error) {
43. try {
44. //準備Request,根據已解析的信息做一些過濾
45. prepareRequest();
46. } catch (Throwable t) {
47. ExceptionUtils.handleThrowable(t);
48. }
49. }
50. if (!error) {
51. try {
52. //調用CoyoteAdapter的service方法,傳入org.apache.coyote.Request對象及org.apache.coyote.Response對象
53. adapter.service(request, response);
54. } catch (InterruptedIOException e) {
55. error = true;
56. }
57. }
58. }