tomcat 組件研究一--啟動過程總結


  作為java 開發者,從開始學習java 便知道tomcat 這個容器了,但是一直卻沒有怎么研究過它的內部結構,以前對tomcat的認識也僅僅局限在那幾個常用的目錄放什么東西,那幾個常用的配置文件應該寫說明內容,卻很少研究其內部的組件以及啟動過程,另外,去網上找相關的資料博客,也發現不是很多很全面,所以這幾天特意了解了下tomcat 的內部工作的原理,簡單總結了tomcat比較核心的一些組件,僅供學習交流,今天這篇博客主要是研究下tomcat 的大體組件有什么以及它們的啟動過程,后面會繼續總結tomcat 處理請求的過程。下面是本篇博客的題綱:

  1、tomcat 主要組件

  2、tomcat動過程

  3、從tomcat 中得到的編程啟迪

一、tomcat主要組件

  下面,先簡單介紹下tomcat的主要組件,讓各位讀者初步認識tomcat的組織結構。

  一直覺得java的抽象建模能力超6,而在研究優秀開源框架的時候,我們也會感到作者將這種語言的建模能力發揮到了極致,tomcat 的組織結構正是這樣一個非常生動的例子,它非常巧妙地地將服務器處理的過程抽象成一個個類。

  先簡單粗略說說常規web應用客戶請求與服務器響應的整個過程吧:客戶發起request-->服務器收到request-->服務器調用服務應用-->業務處理-->返回結果。在這個過程,tomcat把各個涉及到的實體抽象為一個個對象,下圖是tomcat 的大概組織結構圖:

  tomcat內部是如何抽象類的呢?由上面的圖片大概知道,tomcat 把處理請求的的過程分別抽象為如下的類(接口):server -->對應容器本身,代表一個tomcat容器;service-->服務,代表容器下可以提供的服務,service 可以簡單理解為獨立的一個提供服務的項目,tomcat 支持同時運行多個服務,所以一個server 可以有多個service;Connector 和Container 共同構成Service的核心組件;Connector是tomcat的對外連接器,主要負責處理連接相關,它實現了http協議,把數據封裝好為request 對象和response 對象;而Container 是管理容器,它主要負責容器tomcat內部各種servlet。

  上面的圖片說的是大多tomcat的核心組件,他們主要負責處理客戶端請求並返回結果,姑且稱他們為工作組件;另外,還有一些組件,他們主要負責管理這些工作組件的創建、銷毀等管理工作,姑且稱他們為控制組件,這些組件在tomcat里面主要有以下三個:前面提到的代表容器本身的server,server控制了所有工作組件的啟動、停止工作;的Catalina以及Catalina的一個適配器Bootstrap,Catalina 主要是用來啟動tomcat 的server,它是tomcat的總開關,而Bootstrap又控制着Catalina,即用戶點擊startup的啟動程序時,實際上是調用Bootstrap來啟動tomcat的,至於控制類組件中為什么要這么蛋疼硬是分出這么多類(接口)來,后面會說到。

  總的來說,tomcat 中組件他們之間的控制關系(注意,下圖說是控制關系,不是調用關系或者繼承關系)大概如下,總的來說有這樣一種關系,底層的組件的開關由上一層控制。

 

  大概了解了tomcat的組織結構之后,下面總結下tomcat的啟動/初始化(銷毀)機制即tomcat 管理組件生命周期的主要方法。

 

二、tomcat如何管理組件生命周期

  下面總結下tomcat是如何管理內部組件的聲明周期的,主要分以下部分進行總結:一是三個核心控制類的組件如何控制啟動;二是tomcat控制聲明周期的核心接口LifeCycle的講解,下面我就開車了,各位趕緊上車了。

  1、BootStrap 的啟動過程。

  bootStrap 相當於一個Adaptor 即適配器,它通過調用Catalina 來進行tomcat 容器的啟動,其實,Bootstrap 中的main方法就是整個容器的執行入口處,它的源碼也不難看懂,如下(代碼太長折疊了):

 

public static void main(String args[]) {

        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to prevent
            // a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }
View Code

 

  總的來說,Bootstrap  main方法啟動時,會新建一個Bootstrap對象,然后調用該對象的init方法,這個方法會獲取Catalina 的ClassLoder,然后利用反射進行Catalina 對象的創建,創建完對象之后,便會通過傳進來的args[]的參數,調用Catalina對象進行tomcat的start 或者stop等操作。

  由代碼可以看到,start 操作時,Bootstrap 會調用Catalina對象的三個方法:setAwait 、load、以及start方法,Catalina的三個方法解釋如下:setAwait的方法的設置使得Catalina 在啟動Server的時候,Server一直在一個循環中執行wait操作等待請求的到來,load則是加載tomcat啟動必須的數據(例如配置文件等等),最后start 方法則是正真調用Server的啟動方法。

  下面我們再看看Catalina又是如何調用Server的。

  2、Catalina的啟動過程

  上面總結Bootstrap 啟動過程式,有提到Catalina的三個方法:setAwait 、load、以及start,那么,這三個方法在啟動的時候,又是如何工作的呢?首先看下setAwait的源碼是怎么工作的,如下:

public void setAwait(boolean b) {
        await = b;
    }

  好吧,其實它就是設置一個標記量,而這個標記量,主要用在start方法中,我們看在start 方法中,await 有什么用,下面是start 的部分代碼:

if (await) {
       await();
       stop();
}

  這段代碼是在start方法最后的,所以可以知道,setAwait 的作用就是使得執行完start 方法之后,調用本身的await 方法,而查看下面的源碼可知,await 方法的作用就是調用server(getServer 返回server對象)的await 方法。而其實在server中,await方法的作用就是在tomcat 啟動完成之后,處於一種等待請求狀態。

public void await() {

        getServer().await();

    }

  然后,我們再看load方法又做了些什么工作,還是自己看源碼(代碼可能有點長,所以我折起來了):

public void load() {

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed

        initNaming();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            try {
                file = configFile();
                inputStream = new FileInputStream(file);
                inputSource = new InputSource(file.toURI().toURL().toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail", file), e);
                }
            }
            if (inputStream == null) {
                try {
                    inputStream = getClass().getClassLoader()
                        .getResourceAsStream(getConfigFile());
                    inputSource = new InputSource
                        (getClass().getClassLoader()
                         .getResource(getConfigFile()).toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                getConfigFile()), e);
                    }
                }
            }

            // This should be included in catalina.jar
            // Alternative: don't bother with xml, just create it manually.
            if( inputStream==null ) {
                try {
                    inputStream = getClass().getClassLoader()
                            .getResourceAsStream("server-embed.xml");
                    inputSource = new InputSource
                    (getClass().getClassLoader()
                            .getResource("server-embed.xml").toString());
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug(sm.getString("catalina.configFail",
                                "server-embed.xml"), e);
                    }
                }
            }


            if (inputStream == null || inputSource == null) {
                if  (file == null) {
                    log.warn(sm.getString("catalina.configFail",
                            getConfigFile() + "] or [server-embed.xml]"));
                } else {
                    log.warn(sm.getString("catalina.configFail",
                            file.getAbsolutePath()));
                    if (file.exists() && !file.canRead()) {
                        log.warn("Permissions incorrect, read permission is not allowed on the file.");
                    }
                }
                return;
            }

            try {
                inputSource.setByteStream(inputStream);
                digester.push(this);
                digester.parse(inputSource);
            } catch (SAXParseException spe) {
                log.warn("Catalina.start using " + getConfigFile() + ": " +
                        spe.getMessage());
                return;
            } catch (Exception e) {
                log.warn("Catalina.start using " + getConfigFile() + ": " , e);
                return;
            }
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        getServer().setCatalina(this);

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            getServer().init();
        } 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);
            }

        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
        }

    }
View Code

  上面代碼,我們可以看到,load 其實就是分為以下幾個步驟:首先讀取各種啟動所需要的配置文件(context.xml/server.xml等),讀取完之后,創建server對象,當創建server 完成之后,會調用server 的init 方法進行容器的進一步初始化工作,至於init 方法,后面總結server 的時候再詳細總結。

  最后的start 方法,其實就是開啟應用了,下面是start 方法的源碼,由於太長,也折起來了,需要查看請自己展開:

 public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }
View Code

  start 方法其實主要的步驟就是調用server 的start方法進行容器的開啟,相信讀者看源碼也不難理解,這里不累贅說了。

 

  3、server 的啟動過程

  server 是 tomcat 控制類組件的核心組件,它控制着tomcat service 的啟動和關閉,從而達到控制容器開關的目的。server 實際上以一個接口,在tomcat 中,默認實現了這個接口的類是 StandardServer ,在這個server 中,關於啟動階段,主要有兩個方法:initInternal 和startInternal ,兩個方法都是分別調用所有service 的init方法和start  方法,具體可以查看下面的源代碼:

  這個是StandrfServer 的initInternal 的源碼:

protected void initInternal() throws LifecycleException {
        
        super.initInternal();

        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(), "type=StringCache");

        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");
        
        // Register the naming resources
        globalNamingResources.init();
        
        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }
View Code

  可以看到,在源碼里面,initInternal 依次調用了 Service 的init方法,而startInternal 也是類似的,不累贅講了,具體有興趣的讀者可以展開下面的源碼查看:

protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();
        
        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }
View Code

  startInternal  和 initInternal的方法都較為簡單,下面重點研究下Server 的await 方法,也是上面被Catalina 調用的方法,具體請先看一下await 的源碼(代碼過長先折疊起來了):

public void await() {
        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
        if( port == -2 ) {
            // undocumented yet - for embedding apps that are around, alive.
            return;
        }
        if( port==-1 ) {
            try {
                awaitThread = Thread.currentThread();
                while(!stopAwait) {
                    try {
                        Thread.sleep( 10000 );
                    } catch( InterruptedException ex ) {
                        // continue and check the flag
                    }
                }
            } finally {
                awaitThread = null;
            }
            return;
        }

        // Set up a server socket to wait on
        try {
            awaitSocket = new ServerSocket(port, 1,
                    InetAddress.getByName(address));
        } catch (IOException e) {
            log.error("StandardServer.await: create[" + address
                               + ":" + port
                               + "]: ", e);
            return;
        }

        try {
            awaitThread = Thread.currentThread();

            // Loop waiting for a connection and a valid command
            while (!stopAwait) {
                ServerSocket serverSocket = awaitSocket;
                if (serverSocket == null) {
                    break;
                }
    
                // Wait for the next connection
                Socket socket = null;
                StringBuilder command = new StringBuilder();
                try {
                    InputStream stream;
                    long acceptStartTime = System.currentTimeMillis();
                    try {
                        socket = serverSocket.accept();
                        socket.setSoTimeout(10 * 1000);  // Ten seconds
                        stream = socket.getInputStream();
                    } catch (SocketTimeoutException ste) {
                        // This should never happen but bug 56684 suggests that
                        // it does.
                        log.warn(sm.getString("standardServer.accept.timeout",
                                Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
                        continue;
                    } catch (AccessControlException ace) {
                        log.warn("StandardServer.accept security exception: "
                                + ace.getMessage(), ace);
                        continue;
                    } catch (IOException e) {
                        if (stopAwait) {
                            // Wait was aborted with socket.close()
                            break;
                        }
                        log.error("StandardServer.await: accept: ", e);
                        break;
                    }

                    // Read a set of characters from the socket
                    int expected = 1024; // Cut off to avoid DoS attack
                    while (expected < shutdown.length()) {
                        if (random == null)
                            random = new Random();
                        expected += (random.nextInt() % 1024);
                    }
                    while (expected > 0) {
                        int ch = -1;
                        try {
                            ch = stream.read();
                        } catch (IOException e) {
                            log.warn("StandardServer.await: read: ", e);
                            ch = -1;
                        }
                        // Control character or EOF (-1) terminates loop
                        if (ch < 32 || ch == 127) {
                            break;
                        }
                        command.append((char) ch);
                        expected--;
                    }
                } finally {
                    // Close the socket now that we are done with it
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        // Ignore
                    }
                }

                // Match against our command string
                boolean match = command.toString().equals(shutdown);
                if (match) {
                    log.info(sm.getString("standardServer.shutdownViaPort"));
                    break;
                } else
                    log.warn("StandardServer.await: Invalid command '"
                            + command.toString() + "' received");
            }
        } finally {
            ServerSocket serverSocket = awaitSocket;
            awaitThread = null;
            awaitSocket = null;

            // Close the server socket and return
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }
View Code

  這個方法可以看出,該方法的作用就是監聽關閉端口,在接受到請求的時候進行持續的關閉操作,如果端口號是-1,代表不能從外部關閉應用,如果是-2 直接退出;而后面的代碼是我們很熟悉的Socket 編程,主要是監聽對應的關閉端口,如果接受到對應的關閉指令,則會關閉應用。

  4、Service 的啟動過程。

  Service 是代表向外提供的服務,它其實也是一個接口,在tomcat中有一個標准實現StandardService,下面我們就看看這個StandardService的啟動部分的代碼。由上面的server 啟動過程可知,Service 的啟動過程主要是init方法以及start 方法,但是,如果讀者細心閱讀StandardService 的源碼的話,會發現找不到對應service 的init 方法,只是找到了initInternal 方法,其實這時我們大概就可以猜到:StandardService 應該是有父類的,init 方法應該是在父類中,查看StandardService父類LifecycleBase(StandardService 繼承了LifecycleMBeanBase ,LifecycleMBeanBase 繼承了LifecycleBase,而init 方法是在LifecycleBase中的)還真發現有這樣一個方法,同時,我們可以看到,父類的init 方法還調用了一個方法initInternal ,只是這個initInternal 什么都不干,純粹是個模板方法,它由子類(也就是這里的StandardService 實現),這個模板方法的手段在很多開源框架里面都可以看到,也是我們研究開源框架要學習的精粹之一。下面粘上LifecycleBase的源碼:

public final synchronized void init() throws LifecycleException {
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }

        try {
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            initInternal();
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }
View Code

  所以,由上面的代碼可知,我們要研究Service 的啟動過程,其實主要就是研究StandardService中的initInternal 以及startInternal 方法即可,下面我們研究下這兩個方法到底干了什么。

  首先,我們先看看initInternal的源碼:

protected void initInternal() throws LifecycleException {

        super.initInternal();
        
        if (container != null) {
            container.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof LifecycleMBeanBase) {
                ((LifecycleMBeanBase) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    connector.init();
                } catch (Exception e) {
                    String message = sm.getString(
                            "standardService.connector.initFailed", connector);
                    log.error(message, e);

                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new LifecycleException(message);
                }
            }
        }
    }
View Code

  由其源碼可以看到,其實initInternal 主要做了兩件事情:一個是調用Container 的init 方法,另外一個是調用了connectors 的init方法,初始化了所有的connectors類。Container 和Connectors 的作用上面也大概提到了下,兩者分別對應的是Connectors 處理外部請求,Container 則是調用Servlet ,是內部業務的入口,更詳細的后面再展開了。

  然后,我們再看看startInternal 的源碼(其實估計我們不用看源碼都大概可以猜到它要干什么了):

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (container != null) {
            synchronized (container) {
                container.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }
View Code

  沒猜錯,startInternal 其實也是調用Connectors 和Container 的對應start 方法來啟動Connectors 和Container ,但是,我們還發現它調用了Executor的start 方法,其實這個Executor是對應Connectors 中管理線程的線程池,關於Connectors 的工作機制,后面再詳細進行講解。

  總的來說,Service 的啟動過程也不復雜,就是對應的調用了Container 和Connectors 的init 方法和start 方法進行初始化和開啟動作。

 

  5、tomcat 如何管理組件的生命周期?

  由上面對幾個控制組件(BootStrap,Catalina 以及Server)的啟動過程,我們也大概可以知道,tomcat 的啟動流程是怎樣的了,那么,tomcat 又是如何控制組件的生命周期的呢?例如說,我要知道某個組件的狀態或者我想關閉tomcat ,這時候,組件是怎么工作的呢?其實,我們會發現,無論是StandardService 還是StandardServer ,它都有一個共同的父類--LifecycleMBeanBase,而LifecycleMBeanBase又繼承了LifecycleBase,LifecycleBase實現了一個接口:Lifecycle,他們之家你的關系如下圖(手動畫的圖可能會丑了點,各位將就看下,當然,也不太規范,因為傳說中,實現接口應該是用虛線的但是我在win畫圖板找了好久沒找到虛線就作罷了):

 

   其實tomcat 就是通過LifeCycle 這個接口進行組件聲明周期的控制的,下面就研究下LifeCycle這個接口,直接拷貝源碼上來了:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina;


/**
 * Common interface for component life cycle methods.  Catalina components
 * may implement this interface (as well as the appropriate interface(s) for
 * the functionality they support) in order to provide a consistent mechanism
 * to start and stop the component.
 * <br>
 * The valid state transitions for components that support {@link Lifecycle}
 * are:
 * <pre>
 *            start()
 *  -----------------------------
 *  |                           |
 *  | init()                    |
 * NEW -»-- INITIALIZING        |
 * | |           |              |     ------------------«-----------------------
 * | |           |auto          |     |                                        |
 * | |          \|/    start() \|/   \|/     auto          auto         stop() |
 * | |      INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»---  |
 * | |         |                                                            |  |
 * | |destroy()|                                                            |  |
 * | --»-----«--    ------------------------«--------------------------------  ^
 * |     |          |                                                          |
 * |     |         \|/          auto                 auto              start() |
 * |     |     STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
 * |    \|/                               ^                     |  ^
 * |     |               stop()           |                     |  |
 * |     |       --------------------------                     |  |
 * |     |       |                                              |  |
 * |     |       |    destroy()                       destroy() |  |
 * |     |    FAILED ----»------ DESTROYING ---«-----------------  |
 * |     |                        ^     |                          |
 * |     |     destroy()          |     |auto                      |
 * |     --------»-----------------    \|/                         |
 * |                                 DESTROYED                     |
 * |                                                               |
 * |                            stop()                             |
 * ---»------------------------------»------------------------------
 *
 * Any state can transition to FAILED.
 *
 * Calling start() while a component is in states STARTING_PREP, STARTING or
 * STARTED has no effect.
 *
 * Calling start() while a component is in state NEW will cause init() to be
 * called immediately after the start() method is entered.
 *
 * Calling stop() while a component is in states STOPPING_PREP, STOPPING or
 * STOPPED has no effect.
 *
 * Calling stop() while a component is in state NEW transitions the component
 * to STOPPED. This is typically encountered when a component fails to start and
 * does not start all its sub-components. When the component is stopped, it will
 * try to stop all sub-components - even those it didn't start.
 *
 * Attempting any other transition will throw {@link LifecycleException}.
 *
 * </pre>
 * The {@link LifecycleEvent}s fired during state changes are defined in the
 * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the
 * attempted transition is not valid.
 *
 * @author Craig R. McClanahan
 */
public interface Lifecycle {


    // ----------------------------------------------------- Manifest Constants


    /**
     * The LifecycleEvent type for the "component after init" event.
     */
    public static final String BEFORE_INIT_EVENT = "before_init";


    /**
     * The LifecycleEvent type for the "component after init" event.
     */
    public static final String AFTER_INIT_EVENT = "after_init";


    /**
     * The LifecycleEvent type for the "component start" event.
     */
    public static final String START_EVENT = "start";


    /**
     * The LifecycleEvent type for the "component before start" event.
     */
    public static final String BEFORE_START_EVENT = "before_start";


    /**
     * The LifecycleEvent type for the "component after start" event.
     */
    public static final String AFTER_START_EVENT = "after_start";


    /**
     * The LifecycleEvent type for the "component stop" event.
     */
    public static final String STOP_EVENT = "stop";


    /**
     * The LifecycleEvent type for the "component before stop" event.
     */
    public static final String BEFORE_STOP_EVENT = "before_stop";


    /**
     * The LifecycleEvent type for the "component after stop" event.
     */
    public static final String AFTER_STOP_EVENT = "after_stop";


    /**
     * The LifecycleEvent type for the "component after destroy" event.
     */
    public static final String AFTER_DESTROY_EVENT = "after_destroy";


    /**
     * The LifecycleEvent type for the "component before destroy" event.
     */
    public static final String BEFORE_DESTROY_EVENT = "before_destroy";


    /**
     * The LifecycleEvent type for the "periodic" event.
     */
    public static final String PERIODIC_EVENT = "periodic";


    /**
     * The LifecycleEvent type for the "configure_start" event. Used by those
     * components that use a separate component to perform configuration and
     * need to signal when configuration should be performed - usually after
     * {@link #BEFORE_START_EVENT} and before {@link #START_EVENT}.
     */
    public static final String CONFIGURE_START_EVENT = "configure_start";


    /**
     * The LifecycleEvent type for the "configure_stop" event. Used by those
     * components that use a separate component to perform configuration and
     * need to signal when de-configuration should be performed - usually after
     * {@link #STOP_EVENT} and before {@link #AFTER_STOP_EVENT}.
     */
    public static final String CONFIGURE_STOP_EVENT = "configure_stop";


    // --------------------------------------------------------- Public Methods


    /**
     * Add a LifecycleEvent listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener);


    /**
     * Get the life cycle listeners associated with this life cycle. If this
     * component has no listeners registered, a zero-length array is returned.
     */
    public LifecycleListener[] findLifecycleListeners();


    /**
     * Remove a LifecycleEvent listener from this component.
     *
     * @param listener The listener to remove
     */
    public void removeLifecycleListener(LifecycleListener listener);


    /**
     * Prepare the component for starting. This method should perform any
     * initialization required post object creation. The following
     * {@link LifecycleEvent}s will be fired in the following order:
     * <ol>
     *   <li>INIT_EVENT: On the successful completion of component
     *                   initialization.</li>
     * </ol>
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void init() throws LifecycleException;

    /**
     * Prepare for the beginning of active use of the public methods other than
     * property getters/setters and life cycle methods of this component. This
     * method should be called before any of the public methods other than
     * property getters/setters and life cycle methods of this component are
     * utilized. The following {@link LifecycleEvent}s will be fired in the
     * following order:
     * <ol>
     *   <li>BEFORE_START_EVENT: At the beginning of the method. It is as this
     *                           point the state transitions to
     *                           {@link LifecycleState#STARTING_PREP}.</li>
     *   <li>START_EVENT: During the method once it is safe to call start() for
     *                    any child components. It is at this point that the
     *                    state transitions to {@link LifecycleState#STARTING}
     *                    and that the public methods other than property
     *                    getters/setters and life cycle methods may be
     *                    used.</li>
     *   <li>AFTER_START_EVENT: At the end of the method, immediately before it
     *                          returns. It is at this point that the state
     *                          transitions to {@link LifecycleState#STARTED}.
     *                          </li>
     * </ol>
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void start() throws LifecycleException;


    /**
     * Gracefully terminate the active use of the public methods other than
     * property getters/setters and life cycle methods of this component. Once
     * the STOP_EVENT is fired, the public methods other than property
     * getters/setters and life cycle methods should not be used. The following
     * {@link LifecycleEvent}s will be fired in the following order:
     * <ol>
     *   <li>BEFORE_STOP_EVENT: At the beginning of the method. It is at this
     *                          point that the state transitions to
     *                          {@link LifecycleState#STOPPING_PREP}.</li>
     *   <li>STOP_EVENT: During the method once it is safe to call stop() for
     *                   any child components. It is at this point that the
     *                   state transitions to {@link LifecycleState#STOPPING}
     *                   and that the public methods other than property
     *                   getters/setters and life cycle methods may no longer be
     *                   used.</li>
     *   <li>AFTER_STOP_EVENT: At the end of the method, immediately before it
     *                         returns. It is at this point that the state
     *                         transitions to {@link LifecycleState#STOPPED}.
     *                         </li>
     * </ol>
     *
     * Note that if transitioning from {@link LifecycleState#FAILED} then the
     * three events above will be fired but the component will transition
     * directly from {@link LifecycleState#FAILED} to
     * {@link LifecycleState#STOPPING}, bypassing
     * {@link LifecycleState#STOPPING_PREP}
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that needs to be reported
     */
    public void stop() throws LifecycleException;

    /**
     * Prepare to discard the object. The following {@link LifecycleEvent}s will
     * be fired in the following order:
     * <ol>
     *   <li>DESTROY_EVENT: On the successful completion of component
     *                      destruction.</li>
     * </ol>
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void destroy() throws LifecycleException;


    /**
     * Obtain the current state of the source component.
     *
     * @return The current state of the source component.
     */
    public LifecycleState getState();


    /**
     * Obtain a textual representation of the current component state. Useful
     * for JMX.
     */
    public String getStateName();


    /**
     * Marker interface used to indicate that the instance should only be used
     * once. Calling {@link #stop()} on an instance that supports this interface
     * will automatically call {@link #destroy()} after {@link #stop()}
     * completes.
     */
    public interface SingleUse {
    }
}
View Code

  查看源碼,我們可以看到,該接口定義了一組常量,用於 表示tomcat 目前的運行狀態,各個運行狀態之間的關系其實在注釋中有一個非常生動的示意圖,就下面這個截圖(大神簡直用處文本編輯器的新境界有木有!):

 

  OK,具體各個狀態的含義我就不累贅了,這張圖說明了一切。啟動的機制上面我們已經很詳細地討論過了,下面就以stop tomcat這個過程為例,研究下tomcat 如何通過Lifecycle這個接口,控制組件的生命周期。查看源碼可以發現,我們提到的核心組件,包括Connectors 和Container 等組件,它都有有實現或者繼承(Container是個接口,它繼承了LifeCycle 這個接口)LifeCycle 這個接口,當我們從最外層即Bootstrap 停止容器工作的時候,組件之間會依次調用它所管理的組件(例如,Bootstrap 管理着Catalina ,Server 管理着Service)LifeCycle接口的stop 方法,和啟動的過程非常類似,當然,實際的關閉過程是非常復雜的,Connectors 要關閉連接,要銷毀線程,Container 要銷毀業務類以及涉及到的線程等等,所以我們在實際開發中會發現,如果我們強制stop tomcat (例如在eclipse等IDE中直接點擊停止或者直接強退eclipse),那么tomcat 的內部資源時不能有效釋放的,很多時候IDE出現所謂的Pemgen space 錯誤或者oracle中常見的鎖庫現象便有可能是沒有正常釋放資源造成的(反正我就試過很多次由於強退tomcat 導致oracle 的鎖庫的現象)。

  廢話說了那么多,這里簡單總結下tomcat 管理組件生命周期的方法:各個組件都會實現LifeCycle這個接口,而組件之間便是通過這個接口進行組件的生命周期控制的,最頂層的控制類是Bootstrap ,由上而下控制所有組件的停止與開啟。

  好了,到這里,我們可以討論下一開始那個問題了:為什么tomcat 要把Bootstrap 、Catalina 以及Server 這三個控制類獨立出來呢?其實,很多時候,我們都會覺得開源框架的組件或者接口划分有點莫名其妙:為什么要多出這部分組件處理?這個接口有什么用?其實造成這些錯覺的原因是,我們很多時候都沒有考慮擴展性等問題,我們看問題的角度和境界也沒有框架作者那么高,就上面這個例子,個人覺得(不一定對啊,歡迎指正交流)大概是這個原因吧:Tomcat 的啟動方式應該允許有很多個(只是平常我們接觸的可能就那么一種),Bootstrap 是一個適配器,對用戶來說,tomcat 啟動過程是透明的,我只需要調用Bootstrap 即可,如果Bootstrap 和Catalina 合在一起,那不同的啟動方式,肯定要對應不同的Catalina ,而這些啟動方式用戶無須知道,所以就抽象出Bootstrap 這個適配器進行調用不同的啟動方式了;至於Server 和 Catalina 為什么要分開不能合在一起呢?我理解是,其實這也很好理解了,不同的啟動方式,啟動都是同一個Server ,所以要分開。

  當然,其實理解開源框架的那些抽象類、接口,我們最后一面向對象的思維理解,比方說上面的tomcat 類組織結構中,如果把tomcat 比作一個電器,Bootstrap 是一個總開關,Catalina 是開關到電器之間的控制電路,Server代表這個電器,那么我們就知道為什么不能將三者合在一起了:分開更符合現實人的思維,更符合面向對象的思維,因為,開關,開關和電器之間的電路以及電器本身,三者是完全不同的對象。

 

三、研究tomcat 組織結構中得到的一點啟迪

  1、模板方法。

  我們通過上面研究可以發現,類之間的繼承,父類很多時候會調用一個模板方法,這個模板方法具體實現會由子類決定,這就體現出一個很精粹的思想:總體流程在高層設定,而調用者或者繼承者只需要實現某個方法便可以達到某個目的,而這就是框架--框架規定了程序的整個大體架構流程,調用者靈活自己有不同實現  

  當然,模板方法是實現這種效果的常用手段,我們會發現也會利用接口來實現類似的功能,例如spring 中的攔截器,我們只需要實現Interceptor這個接口即可實現攔截功能,這便是類似於模板方法,spring 已經在運行的適當時刻調用Interceptor了,我們只需要專注於我們的Interceptor要干什么就好了,是不是很棒。

  2、面向對象的思維

  剛學java 的時候,覺得,面向過程?面向對象?好像沒什么區別嘛,就是一個封裝了一下,在一個class 里面,一個就是封裝在函數function 里面,還不一樣是代碼?還不一樣地按部就班一步步走?但是,當我們使用了一些開源框架並研究它內部的原理時,你才會正真領悟到面向對象的精粹,才會發現:我靠,還有這種操作!就如上面研究tomcat 的啟動過程,我們如果以面向對象的思維去理解,那就好理解了:tomcat 啟動,那還不簡單,就三個類,一個給客戶用的,開關,Bootstrap;一個tomcat 本身,抽象為server;一個就是連接兩者的Catalina ,它負責具體去如何關tomcat。

  當然,面向對象的思維強大之處還有很多地方,個人也僅僅了解了皮毛中的皮毛,不足之處,各位大佬指正下吧!

  

  本來還想寫完tomcat 處理請求的過程,不過這篇博客太長了,所以這部分還是放到下一篇博客中討論吧,歡迎繼續關注我的下一篇關於tomcat 處理請求的博客。

  end之前,喊下口號,秋招雄起!所有學生黨老鐵拿到好offer!

 

 

 

 

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM