Tomcat源碼分析(一)–服務啟動 – JAVA編程語言程序開發技術文章

  對Tomcat感興趣是由於《深入理解Tomcat》這本書,之前僅僅是使用到瞭Tomcat,這本書卻讓我對Tomcat的實現理解的更加透徹瞭,在這裡希望記錄一些自己對Tomcat的理解。由於這本書是基於tomcat4的,所以我的文章也是基於tomcat4的,但是tomcat的核心思想應該是沒有變的,最主要的兩個組件還是連接器和容器。主要為瞭學習,就不管是新版本還是舊版本瞭。
      為瞭後面的理解,先大致說一下Tomcat的整體架構,Tomcat主要有兩個組件,連接器和容器,所謂連接器就是一個http請求過來瞭,連接器負責接收這個請求,然後轉發給容器。容器即servlet容器,容器有很多層,分別是Engine,Host,Context,Wrapper。最大的容器Engine,代表一個servlet引擎,接下來是Host,代表一個虛擬機,然後是Context,代表一個應用,Wrapper對應一個servlet。從連接器傳過來連接後,容器便會順序經過上面的容器,最後到達特定的servlet。要說明的是Engine,Host兩種容器在不是必須的。(本來想弄張圖的,但是不知道csdn怎麼搞的,插不進去.),實際上一個簡單的tomcat隻要連接器和容器就可以瞭,但tomcat的實現為瞭統一管理連接器和容器等組件,額外添加瞭服務器組件(server)和服務組件(service),添加這兩個東西的原因我個人覺得就是為瞭方便統一管理連接器和容器等各種組件。一個server可以有多個service,一個service包含多個連接器和一個容器,當然還有一些其他的東西,一個父容器又可以包含多個子容器,這些被統一管理的組件都實現瞭Lifecycle接口。隻要一個組件啟動瞭,那麼他的所有子組件也會跟著啟動,比如一個server啟動瞭,它的所有子service都會跟著啟動,service啟動瞭,它的所有連接器和容器等子組件也跟著啟動瞭,這樣,tomcat要啟動,隻要啟動server就行瞭,其他的組件都會跟隨著啟動,那麼server是如何啟動的?再讓我們從頭來看...
  一般啟動Tomcat會是運行startup.bat或者startup.sh文件,實際上這兩個文件最後會調用org.apache.catalina.startup.Bootstrap類的main方法,這個main方法主要做瞭兩件事情,1:定義和初始化瞭tomcat自己的類加載器,2:通過反射調用瞭org.apache.catalina.startup.Catalina的process方法;關鍵代碼如下:
[java]
         ClassLoader commonLoader = null; 
         ClassLoader catalinaLoader = null; 
         ClassLoader sharedLoader = null; 
…………………………初始化以上三個類加載器 
         Class startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 
            Object startupInstance = startupClass.newInstance(); 
……………………………….. 
         methodName = "process";//方法名 
         paramTypes = new Class[1]; 
         paramTypes[0] = args.getClass(); 
         paramValues = new Object[1] 
         paramValues[0] = args; 
         method =startupInstance.getClass().getMethod(methodName, paramTypes); 
         method.invoke(startupInstance, paramValues);//調用process方法 
org.apache.catalina.startup.Catalina的process方法很短,就是下面一點東西:
[java] 
public void process(String args[]) { 
 
     setCatalinaHome(); 
     setCatalinaBase();    www.aiwalls.com
     try { 
         if (arguments(args)) 
             execute(); 
     } catch (Exception e) { 
         e.printStackTrace(System.out); 
     } 
 } 
process的功能也很簡單,1:如果catalina.home和catalina.base兩個屬性沒有設置就設置一下,2:參數正確的話就調用execute方法,execute的方法就是簡單的調用start方法,其中在判斷參數正確的方法arguments中會設置starting標識為true,這樣在execute方法中就能調用start方法,start方法是重點,在它裡面啟動瞭我們的Tomcat所有的服務,下面是start方法裡面一些關鍵的代碼:
[java] 
 protected void start() { 
    Digester digester = createStartDigester(); 
        File file = configFile(); 
        try { 
            InputSource is = 
                new InputSource("file://" + file.getAbsolutePath()); 
            FileInputStream fis = new FileInputStream(file); 
            is.setByteStream(fis); 
            digester.push(this); 
            digester.parse(is); 
            fis.close(); 
        } catch (Exception e) { 
            System.out.println("Catalina.start: " + e); 
            e.printStackTrace(System.out); 
            System.exit(1); 
        } 
…………………… 
    Thread shutdownHook = new CatalinaShutdownHook(); 
 
        // Start the new server 
        if (server instanceof Lifecycle) { 
            try { 
                server.initialize(); 
                ((Lifecycle) server).start(); 
                try { 
                    // Register shutdown hook 
                    Runtime.getRuntime().addShutdownHook(shutdownHook); 
                } catch (Throwable t) { 
                    // This will fail on JDK 1.2. Ignoring, as Tomcat can run 
                    // fine without the shutdown hook. 
                } 
                // Wait for the server to be told to shut down 
                server.await(); 
            } catch (LifecycleException e) { 
                System.out.println("Catalina.start: " + e); 
                e.printStackTrace(System.out); 
                if (e.getThrowable() != null) { 
                    System.out.println("—– Root Cause —–"); 
                    e.getThrowable().printStackTrace(System.out); 
                } 
            } 
        } 

這裡最重要的方法是createStartDigester();和((Lifecycle) server).start();createStartDigester方法主要的作用就是幫我們實例化瞭所有的服務組件包括server,service和connect,至於怎麼實例化的等下再看,start方法就是啟動服務實例瞭。File file = configFile();是新建server.xml文件實例,後面的服務組件都是要根據這個文件來的,現在來看這些服務組件是怎麼實例化的,下面是createStartDigester的部分代碼:
[java] 
protected Digester createStartDigester() { 
 
      // Initialize the digester 
      Digester digester = new Digester(); 
      if (debug) 
          digester.setDebug(999); 
      digester.setValidating(false); 
 
      // Configure the actions we will be using 
      digester.addObjectCreate("Server", 
                               "org.apache.catalina.core.StandardServer", 
                               "className");//創建一個對象 
      digester.addSetProperties("Server"); //設置對象的屬性 
      digester.addSetNext("Server", 
                          "setServer", 
                          "org.apache.catalina.Server");//創建對象間的關系 
 
      digester.addObjectCreate("Server/GlobalNamingResources", 
                               "org.apache.catalina.deploy.NamingResources"); 
      digester.addSetProperties("Server/GlobalNamingResources"); 
      digester.addSetNext("Server/GlobalNamingResources", 
                          "setGlobalNamingResources", 
                          "org.apache.catalina.deploy.NamingResources"); 
 
      digester.addObjectCreate("Server/Listener", 
                               null, // MUST be specified in the element 
                               "className"); 
      digester.addSetProperties("Server/Listener"); 
      digester.addSetNext("Server/Listener", 
                          "addLifecycleListener", 
                          "org.apache.catalina.LifecycleListener"); 
 
      digester.addObjectCreate("Server/Service", 
                               "org.apache.catalina.core.StandardService", 
                               "className"); 
      digester.addSetProperties("Server/Service"); 
      digester.addSetNext("Server/Service", 
                          "addService", 
                          "org.apache.catalina.Service"); 
 
      digester.addObjectCreate("Server/Service/Listener", 
                               null, // MUST be specified in the element 
                               "className"); 
      digester.addSetProperties("Server/Service/Listener"); 
      digester.addSetNext("Server/Service/Listener", 
                          "addLifecycleListener", 
                          "org.apache.catalina.LifecycleListener"); 
 
      digester.addObjectCreate("Server/Service/Connector", 
                               "org.apache.catalina.connector.http.HttpConnector", 
                               "className"); 
      digester.addSetProperties("Server/Service/Connector"); 
      digester.addSetNext("Server/Service/Connector", 
                          "addConnector", 
                          "org.apache.catalina.Connector"); 
 
      digester.addObjectCreate("Server/Service/Connector/Factory", 
                               "org.apache.catalina.net.DefaultServerSocketFactory", 
                               "className"); 
      digester.addSetProperties("Server/Service/Connector/Factory"); 
      digester.addSetNext("Server/Service/Connector/Factory", 
                          "setFactory", 
                          "org.apache.catalina.net.ServerSocketFactory"); 
 
      digester.addObjectCreate("Server/Service/Connector/Listener", 
                               null, // MUST be specified in the element 
                               "className"); 
      digester.addSetProperties("Server/Service/Connector/Listener"); 
      digester.addSetNext("Server/Service/Connector/Listener", 
                          "addLifecycleListener", 
                          "org.apache.catalina.LifecycleListener"); 
       // Add RuleSets for nested elements 
      digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); 
      digester.addRuleSet(new EngineRuleSet("Server/Service/")); 
      digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); //有容器和StandardService的關系 
      digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Default")); 
      digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/DefaultContext/")); 
      digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/Default")); 
      digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/DefaultContext/")); 
      digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); 
      digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); 
 
      digester.addRule("Server/Service/Engine", 
                       new SetParentClassLoaderRule(digester, 
                                                    parentClassLoader)); 
       
     return (digester); 
 
  } 
關鍵是紅色標註的代碼,Digester是一個外部jar包裡面的類,主要的功能就是解析xml裡面的元素並把元素生成對象,把元素的屬性設置成對象的屬性,並形成對象間的父子兄弟等關系。digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");//創建一個org.apache.catalina.core.StandardServer對象,實際上這裡並沒有真正創建出一個對象,而是添加一個模式,隻是後面創建的對象是根據這些模式和server.xml來的,所以可以暫時這麼理解。真正創建對象是在start方法裡面的digester.parse(is),is是server.xml文件的流,digester剛才已經添加瞭StandardServer和StandardService等服務組件,也添加瞭StandardServer和StandardService的關系以及StandardService和連接器HttpConnector,容器StandardHost的關系,所以調用digester.parse(is)方法後就會根據模式和server.xml文件來生成對象以及他們之間的相互關系。這樣我們便有瞭服務器組件StandardServer的對象,也有瞭它的子組件StandardService對象等等,再回到start方法,看下面的代碼:
[java] 
server.initialize(); 
((Lifecycle) server).start(); 
既然有瞭服務器組件的對象,就初始化然後啟動就可以瞭,到此,tomcat就實現瞭啟動服務器組件StandardServer。啟動後做的事情就東西比較多,但是還是比較清晰的,StandardServer的start方法關鍵代碼是啟動它的子組件StandardService:
[java]
// Start our defined Services 
       synchronized (services) { 
           for (int i = 0; i < services.length; i++) { 
               if (services[i] instanceof Lifecycle) 
                   ((Lifecycle) services[i]).start();//調用StandardService的start 
           } 
       } 

StandardService的start方法跟StandardServer的start方法差不多,是啟動它的連接器和容器,上面說瞭一個Service包含一個容器和多個連接器:
[java] 
// Start our defined Container first 
   if (container != null) { 
       synchronized (container) { 
           if (container instanceof Lifecycle) { 
               ((Lifecycle) container).start();//啟動容器 
           } 
       } 
   } 
 
   // Start our defined Connectors second 
   synchronized (connectors) { 
       for (int i = 0; i < connectors.length; i++) { 
           if (connectors[i] instanceof Lifecycle) 
               ((Lifecycle) connectors[i]).start();//啟動連接器 
       } 
   } 

默認的連接器是HttpConnector,所以會調用HttpConnector的start方法,它的方法如下:
[java]
public void start() throws LifecycleException { 
 
    // Validate and update our current state 
    if (started) 
        throw new LifecycleException 
            (sm.getString("httpConnector.alreadyStarted")); 
    threadName = "HttpConnector[" + port + "]"; 
    lifecycle.fireLifecycleEvent(START_EVENT, null); 
    started = true; 
 
    // Start our background thread 
    threadStart();//啟動一個後臺線程,用來處理http請求連接 
 
    // Create the specified minimum number of processors 
    while (curProcessors < minProcessors) { 
        if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) 
            break; 
        HttpProcessor processor = newProcessor();//後臺處理連接的線程 
        recycle(processor); 
    } 
 

這裡有個兩個關鍵的類:HttpConnector和HttpProcessor,它們都實現瞭Runnable接口,HttpConnector負責接收http請求,HttpProcessor負責處理由HttpConnector接收到的請求。註意這裡HttpProcessor會有很多的實例,最大可以有maxProcessor個,初始化是20個。所以在threadStart方法中會啟動一個後臺線程來接收http連接,如下:
[java] 
private void threadStart() { 
 
    log(sm.getString("httpConnector.starting")); 
 
    thread = new Thread(this, threadName); 
    thread.setDaemon(true); 
    thread.start(); 
 

這樣,就會啟動HttpConnector後臺線程,它的run方法不斷循環,主要就是新建一個ServerSocket來監聽端口等待連接,主要代碼如下:
[java] 
 socket = serverSocket.accept(); 
…………………….. 
…………………….. 
 processor.assign(socket); 
serverSocket一直等待連接,得到連接後給HttpProcessor的實例processor來處理,serverSocket則繼續循環監聽,至於processor具體怎麼處理,還有很多要說,這裡先不說。
至此,tomcat啟動完成瞭,StandardServer啟動後,一直到HttpConnector線程等待監聽,就可以處理客戶端的http請求瞭。

作者:haitao111313

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *