Tomcat源碼解讀系列——Tomcat的核心組成和啓動過程

聲明:源碼版本爲Tomcat 6.0.35java

前面的文章中介紹了Tomcat的基本配置,每一個配置項也基本上對應了Tomcat的組件結構,若是要用一張圖來形象展示一下Tomcat組成的話,整個Tomcat的組成能夠以下圖所示:web

Tomcat在接收到用戶請求時,將會經過以上組件的協做來給最終用戶產生響應。首先是最外層的Server和Service來提供整個運行環境的基礎設施,而Connector經過指定的協議和接口來監聽用戶的請求,在對請求進行必要的處理和解析後將請求的內容傳遞給對應的容器,通過容器一層層的處理後,生成最終的響應信息,返回給客戶端。apache

         Tomcat的容器經過實現一系列的接口,來統一處理一些生命週期相關的操做,而Engine、Host、Context等容器經過實現Container接口來完成處理請求時統一的模式,具體表現爲該類容器內部均有一個Pipeline結構,實際的業務處理都是經過在Pipeline上添加Valve來實現,這樣就充分保證整個架構的高度可擴展性。Tomcat核心組件的類圖以下圖所示:網絡

在介紹請求的處理過程時,將會詳細介紹各個組件的做用和處理流程。本文將會主要分析Tomcat的啓動流程,介紹涉及到什麼組件以及初始化的過程,簡單期間將會重點分析HTTP協議所對應Connector啓動過程。架構

Tomcat在啓動時的重點功能以下:socket

  • 初始化類加載器:主要初始化CommonLoader、CatalinaLoader以及SharedLoader;this

  • 解析配置文件:使用Digester組件解析Tomcat的server.xml,初始化各個組件(包含各個web應用,解析對應的web.xml進行初始化);spa

  • 初始化鏈接器:初始化聲明的Connector,以指定的協議打開端口,等待請求。命令行

無論是經過命令行啓動仍是經過Eclipse的WST server UI,Tomcat的啓動流程是在org.apache.catalina.startup. Bootstrap類的main方法中開始的,在啓動時,這個類的核心代碼以下所示:線程

public static void main(String args[]) {
        if (daemon == null) {
            daemon = new Bootstrap();//實例化該類的一個實例
            try {
                daemon.init();//進行初始化
            } catch (Throwable t) {
                ……;
            }
        }
        try {
    ……//此處略去代碼若干行
    if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);//執行load,生成組件實例並初始化
                daemon.start();//啓動各個組件
            }
    ……//此處略去代碼若干行
    }

從以上的代碼中,能夠看到在Tomcat啓動的時候,執行了三個關鍵方法即init、load、和start。後面的兩個方法都是經過反射調用org.apache.catalina.startup.Catalina的同名方法完成的,因此後面在介紹時將會直接轉到Catalina的同名方法。首先分析一下Bootstrap的init方法,在該方法中將會初始化一些全局的系統屬性、初始化類加載器、經過反射獲得Catalina實例,在這裏咱們重點看一下初始化類加載器的方法:

private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

在以上的代碼總,咱們能夠看到初始化了三個類加載器,這三個類加載器將會有篇博文進行簡單的介紹。

而後咱們進入Catalina的load方法:

public void load() {
//……
        //初始化Digester組件,定義瞭解析規則
        Digester digester = createStartDigester();
        //……中間略去代碼若干,主要做用爲將server.xml文件轉換爲輸入流
        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
//經過Digester解析這個文件,在此過程當中會初始化各個組件實例及其依賴關係
            digester.parse(inputSource);
            inputStream.close();
        } catch (Exception e) {
          
        }
        // 調用Server的initialize方法,初始化各個組件
        if (getServer() instanceof Lifecycle) {
            try {
                getServer().initialize();
            } catch (LifecycleException e) {
                if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                    throw new java.lang.Error(e);
                else   
                    log.error("Catalina.start", e);
                
            }
        }

    }

在以上的代碼中,關鍵的任務有兩項即便用Digester組件按照給定的規則解析server.xml、調用Server的initialize方法。關於Digester組件的使用,後續會有一篇專門的博文進行講解,而Server的initialize方法中,會發布事件並調用各個Service的initialize方法,從而級聯完成各個組件的初始化。每一個組件的初始化都是比較有意思的,可是咱們限於篇幅先關注Connector的初始化,這多是最值得關注的。

Connector的initialize方法,核心代碼以下:

public void initialize() throws LifecycleException{
     //該適配器會完成請求的真正處理   
adapter = new CoyoteAdapter(this);
    //對於不一樣的實現,會有不一樣的ProtocolHandler實現類,咱們來看    //Http11Protocol,它用來處理HTTP請求
        protocolHandler.setAdapter(adapter);
        try {
            protocolHandler.init();
        } catch (Exception e) {
            ……
        }
    }

在Http11Protocol的init方法中,核心代碼以下:

public void init() throws Exception {
        endpoint.setName(getName());//endpoint爲JIoEndpoint的實現類
        endpoint.setHandler(cHandler);
        try {
            endpoint.init();//核心代碼就是調用 JIoEndpoint的初始化方法
        } catch (Exception ex) {
           ……
        }
    }

咱們看到最終的初始化方法最終都會調到JIoEndpoint的init方法,網絡初始化和對請求的最初處理都是經過該類及其內部類完成的,因此後續的內容將會重點關注此類:

public void init() throws Exception {
        if (acceptorThreadCount == 0) {//接受請求的線程數
            acceptorThreadCount = 1;
        }
        if (serverSocket == null) {
            try {
                if (address == null) {
    //基於特定端口建立一個ServerSocket對象,準備接受請求
                    serverSocket = serverSocketFactory.createSocket(port, backlog);
                } else {
                    serverSocket = serverSocketFactory.createSocket(port, backlog, address);
                }
            } catch (BindException orig) {
             ……
            }
        }
    }

在上面的代碼中,咱們能夠看到此時初始化了一個ServerSocket對象,用來準備接受請求。

若是將其比做賽跑,此時已經到了「各就各位」狀態,就等最終的那聲「發令槍」了,而Catalina的start方法就是「發令槍」啦:

public void start() {
        if (getServer() == null) {
            load();
        }
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }
        if (getServer() instanceof Lifecycle) {
            try {
                ((Lifecycle) getServer()).start();
            } catch (LifecycleException e) {
                log.error("Catalina.start: ", e);
            }
        }
      //……
 }

此時會調用Server的start方法,這裏咱們重點仍是關注JIoEndpoint的start方法:

public void start()  throws Exception {
        if (!initialized) {
            init();
        }
        if (!running) {
            running = true;
            paused = false;
            if (executor == null) {
    //初始化處理鏈接的線程,maxThread的默認值爲200,這也就是爲何    //說Tomcat只能同時處理200個請求的來歷
                workers = new WorkerStack(maxThreads);
            }
            for (int i = 0; i < acceptorThreadCount; i++) {
    //初始化接受請求的線程
                Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
                acceptorThread.setPriority(threadPriority);
                acceptorThread.setDaemon(daemon);
                acceptorThread.start();
            }
        }
    }

從以上的代碼,能夠看到,若是沒有在server.xml中聲明Executor的話,將會使用內部的一個容量爲200的線程池用來後續的請求處理。而且按照參數acceptorThreadCount的設置,初始化線程來接受請求。而Acceptor是真正的幕後英雄,接受請求並分派給處理過程:

protected class Acceptor implements Runnable {
        public void run() {
            while (running) {
                // 接受發送過來的請求
    Socket socket = serverSocketFactory.acceptSocket(serverSocket);
                    serverSocketFactory.initSocket(socket);
                    //處理這個請求
                    if (!processSocket(socket)) {
                        //關閉鏈接
                        try {
                            socket.close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
            }
        }
    }

從這裏咱們能夠看到,Acceptor接受Socket請求,並調用processSocket方法來進行請求的處理。至此,Tomcat的組件整裝待命,等待請求的到來。關於請求的處理,會在下篇文章中介紹。

相關文章
相關標籤/搜索