一、背景
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.
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裡;
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的代碼;
Java代碼
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方法
Java代碼
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方法
Java代碼
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方法
Java代碼
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, 如下
Java代碼
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範