Tomcat7 源碼閱讀學習
背景
Tomcat源碼目錄結構
Tomcat體系結構
Tomcat源碼解析
Tomcat的啓動流程
Tomcat一次完整請求的處理流程
Tomcat的關閉流程
Tomcat的Connector組件
Tomcat的運行過程中的線程概況及線程模型
Tomcat的類加載機制
Tomcat所涉及的設計模式
參考資源
一、背景
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. }
57. }
58. }
59. }
緊接着CoyoteAdapter會調用Container容器的Pipeline管道一步一步的對Request,Response對象處理。Tomcat容器主要由4層組成,通過管道的模式將各個層的功能進行了獨立,也有利於對各層插入需要的功能。如果需要,只要在server.xml中加入Value元素的配置即可完成擴展功能, CoyoteAdapter的service方法核心代碼如下:
Java代碼
1. public void service(org.apache.coyote.Request req,
2. org.apache.coyote.Response res)
3. throws Exception {
4. Request request = (Request) req.getNote(ADAPTER_NOTES);
5. Response response = (Response) res.getNote(ADAPTER_NOTES);
7. if (request == null) {
8. // Tomcat容器中傳遞的Request和Response都是這裏創建的Request及Response對象
9. //通過Connector創建org.apache.catalina.connector.Request對象
10. request = connector.createRequest();
11. /** 設置org.apache.coyote.Request對象 */
12. request.setCoyoteRequest(req);
14. //通過Connector創建org.apache.catalina.connector.Response對象
15. response = connector.createResponse();
16. //設置org.apache.coyote.Response對象
17. response.setCoyoteResponse(res);
19. //把Request及Response對象相互關聯起來
20. request.setResponse(response);
21. response.setRequest(request);
22. }
24. try {
25. //解析HTTP協議
26. //解析請求的Cookie和sessionId等信息
27. //從Connector中映射此請求對應的StandardHost及StandardContext及StandardWrapper */
28. boolean postParseSuccess = postParseRequest(req, request, res, response);
30. if (postParseSuccess) {
31. //開始調用Tomcat的容器,首先調用StandardEngine容器中管道Pipeline中的第一個Valve,傳入connector.Request和connector.Response
32. connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
33. }
35. //完成此次請求
36. request.finishRequest();
37. //完成此次響應,並提交響應信息
38. //如果response已經提交則直接返回,否則提交response
39. response.finishResponse();
40. } catch (IOException e) {
41. }
43. }
StandardEngin容器默認配置了StandardEnginValve閥門。它主要做負責選擇相應的Host去處理請求,StandardEngineValve的invoke()方法的核心代碼如下:
Java代碼
1. public final void invoke(Request request, Response response)
2. throws IOException, ServletException {
4. // 得到此請求所對應的StandardHost容器
5. Host host = request.getHost();
7. //調用StandardHost容器中管道Pipeline中的第一個Valve
8. host.getPipeline().getFirst().invoke(request, response);
9. }
StandardHost默認情況下配置了ErrorReportValve閥門與StandardHostValue閥門。ErrorReportValve用於處理錯誤日誌。StandardHostValue則選擇相應的Context容器,並且把當前Web應用的WebappClassLoader設置爲線程上下文類加載器,保證我們的Web應用中拿到的當前線程類加載器爲此應用的類加載器, StandardHostValve的invoke()方法的核心代碼如下:
Java代碼
1. public final void invoke(Request request, Response response)
2. throws IOException, ServletException {
4. //得到此次請求所對應的StandardContext容器
5. Context context = request.getContext();
7. if( context.getLoader() != null ) {
8. //線程上下文類加載器切換成當前WebApp的類加載器,從Context的loader中獲取
9. Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
10. }
12. if (asyncAtStart || context.fireRequestInitEvent(request)) {
13. try {
14. //調用StandardContext容器中管道Pipeline中的第一個Valve
15. context.getPipeline().getFirst().invoke(request, response);
16. } catch (Throwable t) {
17. ExceptionUtils.handleThrowable(t);
18. }
19. }
20. //還原StandardClassLoader類加載器爲線程上下文類加載器
21. Thread.currentThread().setContextClassLoader(StandardHostValve.class.getClassLoader());
22. }
StandardContext默認情況下配置了StandardContextValve閥門。對於WEB-INF與META-INF目錄禁止訪問的控制,之後獲取一個此請求的Wrapper容器, StandardContextValve的invoke()方法核核心代碼如下:
Java代碼
1. public final void invoke(Request request, Response response)
2. throws IOException, ServletException {
3. // 對於WEB-INF與META-INF目錄禁止訪問的控制
4. MessageBytes requestPathMB = request.getRequestPathMB();
5. if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
6. || (requestPathMB.equalsIgnoreCase("/META-INF"))
7. || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
8. || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
9. response.sendError(HttpServletResponse.SC_NOT_FOUND);
10. return;
11. }
12. //得到此請求所對應的StandardWrapper容器
13. Wrapper wrapper = request.getWrapper();
15. //調用StandardWrapper容器中管道Pipeline中第一個Valve的invoke()方法
16. wrapper.getPipeline().getFirst().invoke(request, response);
17. }
StandardWrapper容器默認情況下配置了StandardWrapperValve閥門,主要是找到當前請求的需要攔截的過濾器Filter及初始化當前請求的Servlet對象,最終會封裝在FilterChain對象中,責任鏈方式執行, StandardWrapperValve的invoke()方法的核心代碼如下:
Java代碼
1. public final void invoke(Request request, Response response)
2. throws IOException, ServletException {
4. //得到StandardWrapper容器
5. //每個請求都會對應相應的StandardWrapper及StandardWrapperValve對象
6. StandardWrapper wrapper = (StandardWrapper) getContainer();
8. //得到此請求的StandardContext對象
9. Context context = (Context) wrapper.getParent();
11. //得到所請求的Servlet對象
12. Servlet servlet = null;
14. try {
15. if (!unavailable) {
16. //從StandardWrapper容器獲取一個Servlet對象,
Servlet對象的創建及初始化init都在這裏執行
17. servlet = wrapper.allocate();
18. }
19. } catch (UnavailableException e) {}
21. //創建ApplicationFilterFactory對象
22. ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();
24. //創建此次請求的ApplicationFilterChain對象,並設置所請求的Servlet對象
25. //每個請求都會創建一個ApplicationFilterChain對象,包裝了所請求的Servlet對象及一系列攔截的過濾器Filter對象
26. ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet);
28. try {
29. if ((servlet != null) && (filterChain != null)) {
30. //調用ApplicationFilterChain的doFilter方法
31. //傳入org.apache.catalina.connector.RequestFacade及org.apache.catalina.connector.ResponseFacade對象,開始進行請求處理
32. filterChain.doFilter(request.getRequest(),response.getResponse());
33. }
34. } catch (ClientAbortException e) {
35. exception(request, response, e);
36. }
38. try {
39. if (servlet != null) {
40. //執行完後把Servlet實例回收到Servlet實例池
41. wrapper.deallocate(servlet);
42. }
43. } catch (Throwable e) {
44. ExceptionUtils.handleThrowable(e);
45. }
46. }
最後就會進入ApplicationFilterChain,這個也是Tomcat管道處理請求的最後節點,在ApplicationFilterChain中會依次調用Web應用的Filter,然後最終調用當前請求映射的Servlet, ApplicationFilterChain的doFilter()方法核心代碼如下:
Java代碼
1. private void internalDoFilter(ServletRequest request,
2. ServletResponse response)
3. throws IOException, ServletException {
5. //如果還有過濾器Filter,則執行Filter
6. if (pos < n) {
8. //得到過濾器Filter
9. ApplicationFilterConfig filterConfig = filters[pos++];
11. Filter filter = null;
12. try {
13. filter = filterConfig.getFilter();
14. //執行過濾器Filter的doFilter(request,response,FilterChain)方法
15. filter.doFilter(request, response, this);
17. } catch (IOException e) {
18. throw e;
19. }
20. //執行完Filter的doFilter()方法之後直接返回internalDoFilter方法
21. return;
22. }
24. try {
25. //過濾器Filter全部執行完,最終調用Servlet的service(request,response)方法完成Web應用的業務邏輯 */
26. servlet.service(request, response);
27. } catch (IOException e) {
28. throw e;
29. }
30. }
Tomcat一次完成請求的處理流程具體序列圖如下:
3. Tomcat的關閉流程
Tomcat的關閉流程和Tomcat啓動流程類似,都是之前已經啓動的組件依次關閉銷燬,篇幅原因不贅述了。
Tomcat的關閉流程具體序列圖如下:
4. Tomcat的Connector組件
前面也已經經介紹過,Connector組件是Service容器中的一部分。它主要是接收,解析HTTP請求,然後調用本Service下的相關Servlet。由於Tomcat從架構上採用的是一個分層結構,因此根據解析過的HTTP請求,定位到相應Servlet也是一個相對比較複雜的過程,整個Connector實現了從接收Socket到調用Servlet的全過程。先來看一下Connector的功能邏輯:
· 接收Socket
· 從Socket獲取數據包,並解析成HttpServletRequest對象
· 從Engine容器開始走調用流程,經過各層Valve,最後調用Servlet完成業務邏輯
· 返回Response,關閉Socket
可以看出,整個Connector組件是Tomcat運行主幹,目前Connector支持的協議是HTTP和AJP,本文主要是針對HTTP協議的Connector進行闡述。先來看一下Connector的配置,在server.xml裏;
1. <Connector port="80" URIEncoding="UTF-8" protocol="HTTP/1.1"
2. connectionTimeout="20000"
3. redirectPort="7443" />
熟悉的80端口不必說了。"protocol"這裏是指這個Connector支持的協議。針對HTTP協議而言,這個屬性可以配置的值有:
· HTTP/1.1
· org.apache.coyote.http11.Http11Protocol –BIO實現
· org.apache.coyote.http11.Http11NioProtocol –NIO實現
配置"HTTP/1.1"和"org.apache.coyote.http11.Http11Protocol"的效果是一樣的,因此Connector的HTTP協議實現缺省是支持BIO的。無論是BIO還是NIO都是實現一個org.apache.coyote.ProtocolHandler接口,因此如果需要定製化,也必須實現這個接口。 接下來再來看看默認狀態下HTTP Connector的架構及其消息流。
可以看見Connector中三大塊
· Http11Protocol
· Mapper
· CoyoteAdapter
Http11Protocol
類完全路徑org.apache.coyote.http11.Http11Protocol,這是支持HTTP的BIO實現。Http11Protocol包含了JIoEndpoint對象及Http11ConnectionHandler對象。
Http11ConnectionHandler對象維護了一個Http11Processor對象池,Http11Processor對象會調用CoyoteAdapter完成HTTPr Request的解析和分派。
JIoEndpoint維護了兩個線程,Acceptor請求接收線程及Executor請求處理線程池。Acceptor是接收Socket,然後從 Executor請求處理線程池中找出空閒的線程處理socket,如果 Executor請求處理線程池沒有空閒線程,請求會被放入阻塞隊列等待有空閒線程。
Mapper
類完全路徑org.apache.tomcat.util.http.mapper.Mapper,此對象維護了一個從Host到Wrapper的各級容器的快照。它主要是爲了,當HTTP Request被解析後,能夠將HTTP Request綁定到相應的Host,Context,Wrapper(Servlet),進行業務處理;
CoyoteAdapter
類完全路徑org.apache.catalina.connector.CoyoteAdapter,此對象負責將http request解析成HttpServletRequest對象,之後綁定相應的容器,然後從Engine開始逐層調用Valve直至該Servlet。比如根據Request中的JSESSIONID綁定服務器端的相應Session。這個JSESSIONID按照優先級或是從Request URL中獲取,或是從Cookie中獲取,然後再Session池中找到相應匹配的Session對象,然後將其封裝到HttpServletRequest對象,所有這些都是在CoyoteAdapter中完成的。看一下將Request解析爲HttpServletRequest對象後,開始調用Servlet的代碼;
1. connector.getContainer().getPipeline().getFirst().invoke(request, response);
Connector的拿到的容器就是StandardEngine,代碼的可讀性很強,獲取StandardEngine的Pipeline,然後從第一個Valve開始調用邏輯。
Tomcat的Connector組件工作具體序列圖入下:
5. Tomcat運行過程中的線程概況及線程模型
Tomcat在運行過程中會涉及到很多線程,主要的線程有Tomcat啓動時的main主線程(Java進程啓動時的那個線程), 子容器啓動與關係線程池(用來啓動關係子容器),Tomcat後臺線程(Tomcat內置的後臺線程,比如用來熱部署Web應用), 請求接收線程(用來接收請求的線程), 請求處理線程池(用來處理請求的線程)。理解了Tomcat運行過程中的主要線程,有助於我們理解整個系統的線程模型。下面是每個線程的源碼及功能的詳細分析:
Java代碼
1. (1) main主線程:即從main開始執行的線程,在啓動Catalina之後就一直在Server的await方法中等待關閉命令,如果未接收到關閉命令就一直等待下去。main線程會一直阻塞着等待關機命令。
2. 啓動過程:Bootstrap.main>>
3. Catalina.start >>
4. Catalina.await() >>
5. StandardServer.await()
7. (2) 子容器啓動與關閉線程:ContainerBase的protected ThreadPoolExecutor startStopExecutor容器線程池執行器[處理子容器的啓動和停止]在ContainerBase.initInternal()方法中進行初始化 作用:在ContainerBase.startInternal()方法中啓動子容器,在ContainerBase.stopInternal()方法中停止子容器,即處理子容器的啓動和停止事件。
8. 子容器線程池創建過程:StandardService.initInternal>>
9. ContainerBase.initInternal >>
10. new ThreadPoolExecutor()
11. 啓動過程:StandardService.startInternal>>
12. ContainerBase.startInternal >>
13. startStopExecutor.submit(new StartChild(Child)或StopChild(Child))>>
14. execute(ftask) >>
15. addIfUnderCorePoolSize(command)
16. addIfUnderCorePoolSize內容如下:
17. private boolean addIfUnderCorePoolSize(Runnable firstTask) {
18. Thread t = null;
19. final ReentrantLock mainLock = this.mainLock;
20. mainLock.lock();
21. try {
22. if (poolSize < corePoolSize && runState == RUNNING)
23. t = addThread(firstTask);
24. } finally {
25. mainLock.unlock();
26. }
27. if (t == null)
28. return false;
29. t.start(); // StartChild.call()被調用,調用子容器的child.start()啓動子容器
30. return true;
31. }
32. private Thread addThread(Runnable firstTask) {
33. Worker w = new Worker(firstTask);
34. Thread t = threadFactory.newThread(w); //創建一個線程
35. if (t != null) {
36. w.thread = t;
37. workers.add(w);
38. int nt = ++poolSize;
39. if (nt > largestPoolSize)
40. largestPoolSize = nt;
41. }
42. return t;
43. }
45. (3) 容器的後臺處理線程:ContainerBackgroundProcessor是ContainerBase的一個內部類
46. 啓動過程:StandardService.startInternal>>
47. ContainerBase.startInternal >>
48. ContainerBase.threadStart()
49. ContainerBase.startInternal(){
50. for(int i=0 ;i < children.length;i++){
51. startStopExecutor.submit(new StartChild(children[i]); //起新線程啓動子容器
52. }
53. threadStart(); // 在啓動容器的最後調用threadStart方法
54. }
55. protected void threadStart() {
56. if (thread != null)
57. return;
58. // backgroundProcessorDelay默認爲-1,即不產生後臺線程,但StandardEngine的構造
59. // 函數中設置backgroundProcessorDelay = 10,即默認只有Engine產生後臺線程,
60. // 其它子容器共享Engine的後臺線程
61. if (backgroundProcessorDelay <= 0)
62. return;
63. threadDone = false;
64. String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
65. thread = new Thread(new ContainerBackgroundProcessor(), threadName);
66. thread.setDaemon(true);
67. thread.start(); // 啓動容器後臺線程
68. }
70. (4) 請求接收線程:即運行Acceptor的線程,啓動Connector的時候產生
71. 啓動過程:StandardService.startInternal>>
72. Connector.startInternal() >>
73. protocolHandler.start() >>
74. AbstractProtocol.start() >>
75. endpoint.start() >>
76. AbstractEndpoint.Start() >>
77. JioEndpoint.startInternal() >>
78. JIoEndpoint.startAcceptorThreads()
79. startAcceptorThreads內容如下:
80. protected final void startAcceptorThreads() {
81. int count = getAcceptorThreadCount(); // 默認只有一個接收線程Acceptor
82. acceptors = new Acceptor[count];
83. for (int i = 0; i < count; i++) {
84. acceptors[i] = createAcceptor(); // org.apache.tomcat.util.net. JIoEndpoint.Acceptor
85. Thread t = new Thread(acceptors[i], getName() + "-Acceptor-" + i); //創建請求接收線程
86. t.setPriority(getAcceptorThreadPriority());
87. t.setDaemon(getDaemon());
88. t.start(); // 啓動請求接收線程來接收請求
89. }
90. }
91. 這樣在Acceptor的run()中就可以接收請求了。
92.
(5) 請求處理線程:即運行SocketProcessor的線程,從請求處理線程池中產生,org.apache.tomcat.util.net. AbstractEndpoint的成員變量private Executor executor用來處理請求,org.apache.tomcat.util.net.JioEndpoint(繼承了AbstractEndpoint)在方法startInternal()中調用createExecutor()來創建executor爲線程池對象ThreadPoolExecutor
93. 請求處理線程池創建過程:StandardService.startInternal>>
94. Connector.startInternal >>
95. protocolHandler.start >>
96. AbstractProtocol.start >>
97. endpoint.start >>
98. AbstractEndpoint.Start >>
99. JIoEndpoint.startInternal >>
100. JIoEndpoint.createExecutor>>
101. new ThreadPoolExecutor()
102. public void createExecutor() {
103. internalExecutor = true;
104. TaskQueue taskqueue = new TaskQueue();
105. // 線程池線程產生的的工廠爲java.util.concurrent.ThreadFactory.TaskThreadFactory
106. TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
107. executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
108. taskqueue.setParent( (ThreadPoolExecutor) executor);
109. }
110. 啓動過程:Acceptor.run()>> // 請求接收線程接收到請求後
111. processSocket(socket)>>
112. getExecutor().execute(new SocketProcessor(wrapper))>> // 交給請求處理線程處理
113. ThreadPoolExecutor.execute (command,0,TimeUnit.MILLISECONDS)>>
114. super.execute(command) >>
115. addIfUnderCorePoolSize(command)
116. addIfUnderCorePoolSize內容如下:
117. private boolean addIfUnderCorePoolSize(Runnable firstTask) {
118. Thread t = null;
119. final ReentrantLock mainLock = this.mainLock;
120. mainLock.lock();
121. try {
122. if (poolSize < corePoolSize && runState == RUNNING)
123. t = addThread(firstTask);
124. } finally {
125. mainLock.unlock();
126. }
127. if (t == null)
128. return false;
129. t.start(); // SocketProcessor.run運行
130. return true;
131. }
132. private Thread addThread(Runnable firstTask) {
133. Worker w = new Worker(firstTask);
134. Thread t = threadFactory.newThread(w); //創建一個線程
135. if (t != null) {
136. w.thread = t;
137. workers.add(w);
138. int nt = ++poolSize;
139. if (nt > largestPoolSize)
140. largestPoolSize = nt;
141. }
142. return t;
143. }
144. 至此SocketProcessor.run運行起來了,這個線程負責處理請求。
Tomcat運行過程中的線程概況具體序列圖入下:
大家都知道Java中的IO模型分阻塞式的IO模型(BIO)及非阻塞式的IO模型(NIO),Tomcat中的對請求接收網絡IO模塊中也同樣實現了基於上述兩種IO的線程模型,具體的實現如下,
Tomcat基於阻塞式IO(BIO)的線程模型序列圖如下:
Tomcat基於非阻塞式IO(NIO)的線程模型序列圖如下:
6. Tomcat的類加載機制
主流的JavaWeb服務器如(Tomcat,Jetty,WebLogic)都實現了自己定義的類加載器(一般不止一個),因爲Web服務器一般要解決如下幾個問題:
1、部署在同一個服務器上的兩個Web應用程序所使用的Java類庫可以實現相互隔離。
2、部署在同一個服務器上的兩個應用程序所使用的Java類庫可以相互共享。
3、服務器需要儘可能地保證自身的安全不受部署的Web應用程序的影響。
爲了解決這幾個問題,Tomcat實現瞭如下類加載器的層級結構:
Tomcat7運行時類的加載說明:
1)Bootstrap Class Loader是JVM的內核由C實現的,加載了JVM的核心包rt.jar。rt.jar中的所有類執行其class的getClassLoader()方法都將返回null,例如Integer.class.getClassLoader()。
2)Extension Class Loader主要加載了JVM擴展包中相關的jar包。例如運行下列代碼將System.out.println(ZipPath.class.getClassLoader());將得到如下的運行結果:sun.misc.Launcher$ExtClassLoader。
3)System Class Loader加載CLASSPATH相關的類,例如在Tomcat的Bootstrap的main方法中執行System.out.println(Bootstrap.class.getClassLoader());則將得到:sun.misc.Launcher$AppClassLoader。
4)Common Class Loader,Tomcat7中的CATALINA_HOME/lib下的jar包。注意Tomcat在啓動文件中將啓動時配置了-classpath "%CATALINA_HOME%\lib\catalina.jar"因此catalina.jar中的類雖然指定使用類加載器Common Class Loader,但是按JVM的委託加載原則System.out.println(Bootstrap.class.getClassLoader());得到的類加載器是:sun.misc.Launcher$AppClassLoader。
5)Webapp Class Loader, 主要負責加載Context容器中的所有的類。實際上該加載器提供了參數delegateLoad供用戶設定是否使用parent-first加載。默認該值爲false,默認用parent-last加載。出於安全性的考慮對於核心類WebappClassLoader是不允許加載的。包括:java.,javax.servlet.jsp.jstl,javax.servlet.,javax.el。
Tomcat類加載器的相關類圖
Tomcat類加載器的相關源代碼分析
(一) ClassLoader的load方法
1. protected Class<?> loadClass(String name, boolean resolve)
2. throws ClassNotFoundException
3. {
4. synchronized (getClassLoadingLock(name)) {
5. // First, check if the class has already been loaded
6. Class c = findLoadedClass(name);
7. if (c == null) {
8. try {
9. if (parent != null) {
10. c = parent.loadClass(name, false);
11. } else {
12. c = findBootstrapClassOrNull(name);
13. }
14. }
15. if (c == null) {
16. // If still not found, then invoke findClass in order
17. // to find the class.
18. long t1 = System.nanoTime();
19. c = findClass(name);
20. }
21. }
22. if (resolve) {
23. resolveClass(c);
24. }
25. return c;
26. }
27. }
1)在該方法中,首先檢查是否已經加載了該類,這裏有個問題JVM如何判斷一個類是否被加載過的?這裏涉及到了類的命名空間問題。在JAVA中判斷一個類是否相同不僅看類名是否相同,還要看其類加載器是否相同。同一個類可以被不同的類加載器所加載,並且認爲是不同的。該問題可以分解下面兩個方面看。
a) 單一加載原則:在加載器鏈中,一個類只會被鏈中的某一個加載器加載一次。而不會被重複加載。實現類的共享,Tomcat多個應用,如果需要共享一些jar包,那麼只需要交給 commonClassLoader加載,那麼所有的應用就可以共享這些類。
b) 可見性原則:父加載器加載的類,子加載器是可以訪問的。而自加載器所加載的類,父加載器無法訪問。不同加載器鏈之間其是相互不可見,無法訪問的。實現隔離,Tomcat就是應 用該特性,爲每一個Context容器創建一個WebappClassLoader類加載器對象,從而實現了應用間的相互隔離。應用間的類是不可見的所以無法相互訪問。
2) 如果步驟一中無緩存,查看該類父加載器,如果存在那麼委託給付加載器。如果沒有父加載器那麼認爲BootstrapClassLoader是其父加載器,委託進行加載。
3)如果父加載器無法加載則拋出ClassNotFoundException,調用抽象方法findClass方法。
4)此處的resolveClass方法指的是上文類加載過程中連接的第三步操作。resolve該類的形式引用等等。
(二)類URLClassLoader的findClass方法
1. protected Class<?> findClass(final String name)
2. throws ClassNotFoundException
3. {
4. try {
5. return AccessController.doPrivileged(
6. new PrivilegedExceptionAction<Class>() {
7. public Class run() throws ClassNotFoundException {
8. String path = name.replace('.', '/').concat(".class");
9. Resource res = ucp.getResource(path, false);
10. if (res != null) {
11. try {
12. return defineClass(name, res);
13. } catch (IOException e) {
14. throw new ClassNotFoundException(name, e);
15. }
16. } else {
17. throw new ClassNotFoundException(name);
18. }
19. }
20. }, acc);
21. } catch (java.security.PrivilegedActionException pae) {
22. throw (ClassNotFoundException) pae.getException();
23. }
24. }
該方法的核心是加載獲取Java類字節碼文件的字節流,然後調用父類的defineClass方法完成類的構造過程。defineClass是由JVM實現的,不允許被覆寫,因此用戶類文件就必須遵循 JVM的文件規範才能被正確的解析。
(三)WebappClassLoader重寫了ClassLoader的loadClass方法
1. @Override
2. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
3. Class<?> clazz = null;
5. // (0) 當前對象緩存中檢查是否已經加載該類
6. clazz = findLoadedClass0(name);
8. // (0.1) 檢查JVM的緩存,是否已經加載過該類
9. clazz = findLoadedClass(name);
11. // (0.2) 防止加載一些系統相關的類
12. try {
13. clazz = system.loadClass(name);
14. if (clazz != null) {
15. if (resolve)
16. resolveClass(clazz);
17. return (clazz);
18. }
19. } catch (ClassNotFoundException e) {}
21. boolean delegateLoad = delegate || filter(name);
23. // (1) 如果配置了parent-first模式,那麼委託給父加載器
24. if (delegateLoad) {
25. ClassLoader loader = parent;
26. if (loader == null) loader = system;
27. try {
28. clazz = Class.forName(name, false, loader);
29. if (clazz != null) {
30. if (resolve) resolveClass(clazz);
31. return (clazz);
32. }
33. } catch (ClassNotFoundException e) {}
34. }
36. // (2) 從WebApp中去加載類,主要是WebApp下的/classes目錄與/lib目錄
37. try {
38. clazz = findClass(name);
39. if (clazz != null) {
40. if (resolve)
41. resolveClass(clazz);
42. return (clazz);
43. }
44. } catch (ClassNotFoundException e) {}
46. // (3) 如果在當前WebApp中無法加載到,委託給StandardClassLoader從$catalina_home/lib中去加載
47. if (!delegateLoad) {
48. ClassLoader loader = parent;
49. if (loader == null)
50. loader = system;
51. try {
52. clazz = Class.forName(name, false, loader);
53. if (clazz != null) {
54. if (resolve)
55. resolveClass(clazz);
56. return (clazz);
57. }
58. } catch (ClassNotFoundException e) {}
59. }
61. throw new ClassNotFoundException(name);
62. }
(四)WebappClassLoader的findClass()方法>>findClassInternal()方法>>findResourceInternal()方法
Java代碼
1. //從WebappClassLoader的classpath中加載類或資源文件
2. protected ResourceEntry findResourceInternal(String name, String path) {
3. if (!started) {
4. log.info(sm.getString("webappClassLoader.stopped", name));
5. return null;
6. }
7. ResourceEntry entry = resourceEntries.get(name);
8. if (entry != null)
9. return entry;
11. int contentLength = -1;
12. InputStream binaryStream = null;
14. int jarFilesLength = jarFiles.length;
15. int repositoriesLength = repositories.length;
17. int i;
18. Resource resource = null;
19. boolean fileNeedConvert = false;
21. //首先從/WEB-INF/classes路徑中加載類
22. for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
23. try {
24. String fullPath = repositories[i] + path;
25. //獲取資源的絕對路徑
26. Object lookupResult = resources.lookup(fullPath);
27. if (lookupResult instanceof Resource) {
28. resource = (Resource) lookupResult;
29. }
31. ResourceAttributes attributes =
32. (ResourceAttributes) resources.getAttributes(fullPath);
33. contentLength = (int) attributes.getContentLength();
34. String canonicalPath = attributes.getCanonicalPath();
35. if (canonicalPath != null) {
36. entry = findResourceInternal(new File(canonicalPath), "");
37. } else {
38. //獲取類或文件的ResourceEntry
39. entry = findResourceInternal(files[i], path);
40. }
41. entry.lastModified = attributes.getLastModified();
43. if (resource != null) {
44. try {
45. //得到類或資源的輸入流InputStream
46. binaryStream = resource.streamContent();
47. } catch (IOException e) {
48. return null;
49. }
50. if (needConvert) {
51. if (path.endsWith(".properties")) {
52. fileNeedConvert = true;
53. }
54. }
55. }
56. } catch (NamingException e) {
57. }
58. }
60. //然後再從/WEB-INF/lib路徑中加載類
61. if ((entry == null) && (notFoundResources.containsKey(name)))
62. return null;
63. JarEntry jarEntry = null;
64. synchronized (jarFiles) {
65. try {
66. for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
67. //獲取JarFile下的JarEntry
68. jarEntry = jarFiles[i].getJarEntry(path);
70. if (jarEntry != null) {
71. entry = new ResourceEntry();
72. try {
73. //設置類或文件的URL
74. entry.codeBase = getURL(jarRealFiles[i], false);
75. String jarFakeUrl = getURI(jarRealFiles[i]).toString();
76. jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
77. //設置URL
78. entry.source = new URL(jarFakeUrl);
79. entry.lastModified = jarRealFiles[i].lastModified();
80. } catch (MalformedURLException e) {
81. return null;
82. }
83. contentLength = (int) jarEntry.getSize();
84. try {
85. entry.manifest = jarFiles[i].getManifest();
86. //從JarFile中根據JarEntry獲取jar包中類的輸入流InputStream
87. binaryStream = jarFiles[i].getInputStream(jarEntry);
88. } catch (IOException e) {
89. return null;
90. }
91. }
92. }
94. if (binaryStream != null) {
95. byte[] binaryContent = new byte[contentLength];
96. int pos = 0;
97. try {
98. while (true) {
99. //從輸入流InputStream中讀取類或文件的二進制流
100. int n = binaryStream.read(binaryContent, pos, binaryContent.length - pos);
101. if (n <= 0)
102. break;
103. pos += n;
104. }
105. } catch (IOException e) {
106. return null;
107. }
108. //設置二進制設置到ResourceEntry
109. entry.binaryContent = binaryContent;
110. }
111. }
112. }
113. synchronized (resourceEntries) {
114. ResourceEntry entry2 = resourceEntries.get(name);
115. if (entry2 == null) {
116. //向本地資源緩存這注冊ResourceEntry
117. resourceEntries.put(name, entry);
118. } else {
119. entry = entry2;
120. }
121. }
122. return entry;
123. }
(五)Web應用中經常使用到的線程上下文類加載器的在Tomcat中的設置實現
在Web應用中我們經常用到線程上下文類加載器,拿到的肯定是當前WebApp的WebAppClassLoader, 如下
1. ClassLoader classLoader= Thread.currentThread().getContextClassLoader();
我們用到的線程上下文類加載器其實是在StandardHostValve的invoke()方法中被設置的
Java代碼
1. public final void invoke(Request request, Response response)
2. throws IOException, ServletException {
4. //得到此次請求所對應的StandardContext容器
5. Context context = request.getContext();
7. if( context.getLoader() != null ) {
8. //線程上下文類加載器切換成當前WebApp的類加載器,從Context的loader中獲取
9. Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
10. }
12. if (asyncAtStart || context.fireRequestInitEvent(request)) {
13. try {
14. //調用StandardContext容器中管道Pipeline中的第一個Valve,直到調用Servlet
15. context.getPipeline().getFirst().invoke(request, response);
16. } catch (Throwable t) {
17. ExceptionUtils.handleThrowable(t);
18. request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
19. throwable(request, response, t);
20. }
21. }
22. if (!Globals.IS_SECURITY_ENABLED) {
23. //還原StandardClassLoader類加載器爲線程上下文類加載器
24. Thread.currentThread().setContextClassLoader(StandardHostValve.class.getClassLoader();
25. }
26. }
7、Tomcat所涉及的設計模式
Tomcat雖然代碼比較龐大,但是整體還是設計的比較優雅,特別是很多組件化的設計思路,其中涉及到的一些常用的設計模式值得我們學習及借鑑:
責任鏈模式
Tomcat中有兩個地方比較明顯的使用了責任鏈模式,一、Tomcat中的ApplicationFilterChain實現了Filter攔截和實際Servlet的請求,是典型的責任鏈模式。其他開源框架中類似的設計還有Struts2中的DefaultActionInvocation實現Interceptor攔截和Action的調用。Spring AOP中ReflectiveMethodInvocation實現MethodInceptor方法攔截和target的調用。二、Tomcat中的Pipeline-Valve模式也是責任鏈模式的一種變種,從Engine到Host再到Context一直到Wrapper都是通過一個鏈來傳遞請求。
觀察者模式
Tomcat通過LifecycleListener對組件生命週期組件Lifecycle進行監聽就是典型的觀察者模式,各個組件在其生命期中會有各種各樣行爲,而這些行爲都會觸發相應的事件,Tomcat就是通過偵聽這些事件達到對這些行爲進行擴展的目的。在看組件的init和start過程中會看到大量如:lifecycle.fireLifecycleEvent(AFTER_START_EVENT,null);這樣的代碼,這就是對某一類型事件的觸發,如果你想在其中加入自己的行爲,就只用註冊相應類型的事件即可。
門面模式
門面設計模式在 Tomcat 中有多處使用,在 Request 和 Response 對象封裝中(RequestFacade,ResponseFacade)、ApplicationContext 到ApplicationContextFacade等都用到了這種設計模式。這種設計模式主要用在一個大的系統中有多個子系統組成時,這多個子系統肯定要涉及到相互通信,但是每個子系統又不能將自己的內部數據過多的暴露給其它系統,不然就沒有必要劃分子系統了。每個子系統都會設計一個門面,把別的系統感興趣的數據封裝起來,通過這個門面來進行訪問。
模板方法模式
模板方法模式是我們平時開發當中常用的一種模式,把通用的骨架抽象到父類中,子類去實現特地的某些步驟。Tomcat及Servlet規範API中也大量的使用了這種模式,比如Tomcat中的ContainerBase中對於生命週期的一些方法init,start,stop和Servlet規範API中的GenericServlet中的service抽象骨架模板方法均使用了模板方法模式。
參考資源
Tomcat官網:http://tomcat.apache.org/
<How Tomcat Wroks>:Tomcat5的架構分析
Tomcat源碼分析:http://zddava.iteye.com/blog/305944
Tomat的源碼環境搭建:http://mabusyao.iteye.com/blog/1198557
<Servlet2.5規範>:JSR制定的規Servlet範