Tomcat源碼解析系列(二)Catalina

前言
上篇文章說道Tomcat的Bootstrap類,在 start 的時候,分別調用了 Bootstrap 類的java

daemon.setAwait(true);
daemon.load(args);
daemon.start();

這三個方法,這三個方法都相似,都是經過反射調用 Catalina 類的同名方法,setAwait 方法比較簡單,就是把 Catalina 的 await 屬性設置爲true。本篇文章着重看一下 load 方法,和 start 方法apache


1. load 方法segmentfault

private void load(String[] arguments)
        throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments == null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);

    }

catalinaDaemon 就是 Catalina 對象,上面這段代碼邏輯很簡單,就是調用 catalinaDaemon 對象的 load() 或者 load(String[]) 方法。load(String[])其實也是調用 load() 方法。
1.1. Catalina#load tomcat

public void load() {

        if (loaded) {
            return;
        }
        loaded = true;

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed
        initNaming();

        // Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
        File file = configFile();

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

        try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
            InputStream inputStream = resource.getInputStream();
            InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
        } catch (Exception e) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml"), e);
            } else {
                log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
                if (file.exists() && !file.canRead()) {
                    log.warn(sm.getString("catalina.incorrectPermissions"));
                }
            }
            return;
        }

        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // 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(sm.getString("catalina.initError"), e);
            }
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
        }
    }

org.apache.catalina.startup.Catalina#load() 方法的邏輯挺清晰的,大概能夠分爲三段,第一個 try-catch 以前算一段,第一個 try-catch 算第二段,第一個 try-catch 到第二個 try-catch 結束算第三段。
1.1.1. 第一段 ide

先看第一段,第一段中的 initDirs() 方法和 initNaming() 方法比較簡單,能夠忽略。而後是this

ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));

這一行代碼代碼的做用是設置Tomcat要加載的配置的配置源,也就是 conf 目錄下的 server.xml 文件。
而後線程

Digester digester = createStartDigester();

這一行建立了一個 Digester 對象,這個對象是用來解析 server.xml 文件的debug

/**
     * Create and configure the Digester we will be using for startup.
     * @return the main digester to parse server.xml
     */
    protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);
         ……

        // 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.NamingResourcesImpl");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResourcesImpl");
       ……
        return digester;

    }

從註釋和大部分類似的代碼能夠看出,Digester 就是用來解析 server.xml 並建立對應的默認實現類對象的。好比碰到 <Server> 標籤就建立默認的 StarndardServer 類對象。關於 Digester 類就很少講了,有興趣的話能夠自行研究一下。code

1.1.2. 第二段
有了這個 Digester 對象,就能夠解析出 server.xml 裏寫的各類 xml 標籤了,第一個 try-catch 就是調用 digester.parse 方法來解析 server.xml,具體的解析過程就不往下跟了,其實就是 用 SAXParser 來解析 xml,解析完了以後,xml 裏定義的各類標籤就有對應的實現類對象了。component

1.1.3. 第三段

getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // 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(sm.getString("catalina.initError"), e);
            }
        }

其中 getServer() 獲取的是 Catalina 對屬性

/**
     * The server component we are starting or stopping.
     */
    protected Server server = null;

這個屬性是在 digester.parse 解析 xml 的時候賦值的
這段代碼的前三行首先設置了 Server 對象的一些屬性,而後到 try-catch 裏調用 Server 對象的 init 方法。Server 對象的 init 方法涉及的東西比較多,將在下一篇文章中具體講。

2. start 方法
在 load 方法以後,Tomcat 就初始化了一系列的組件,接着就能夠調用 start 方法進行啓動了。

/**
     * Start a new server instance.
     */
    public void start() {

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

        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            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(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
        }

        // 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();
        }
    }

上面這段代碼,邏輯很是簡單,首先肯定 getServer() 方法不爲 null ,也就是肯定 server 屬性不爲null,而 server 屬性是在 load 方法就初始化了。
整段代碼的核心就是 try-catch 裏的 getServer().start() 方法了,也就是調用 Server 對象的 start() 方法來啓動 Tomcat。本篇文章就先不對 Server 的 start() 方法進行解析了,下篇文章會單獨講。
調用完 Server#start 方法以後,註冊了一個ShutDownHook,也就是 CatalinaShutdownHook 對象,

/**
     * Shutdown hook which will perform a clean shutdown of Catalina if needed.
     */
    protected class CatalinaShutdownHook extends Thread {

        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }

CatalinaShutdownHook 的邏輯也簡單,就是調用 Catalina 對象的 stop 方法來中止 tomcat。
最後就進入 if 語句了,await 是在 Bootstrap 裏調用的時候設置爲 true 的,也就是本文開頭的時候提到的三個方法中的一個。await 方法的做用是停住主線程,等待用戶輸入shutdown 命令以後,中止等待,以後 main 線程就調用 stop 方法來中止Tomcat。

/**
     * Stop an existing server instance.
     */
    public void stop() {

        try {
            // Remove the ShutdownHook first so that server.stop()
            // doesn't get invoked twice
            if (useShutdownHook) {
                Runtime.getRuntime().removeShutdownHook(shutdownHook);

                // If JULI is being used, re-enable JULI's shutdown to ensure
                // log messages are not lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                            true);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
            // fine without the shutdown hook.
        }

        // Shut down the server
        try {
            Server s = getServer();
            LifecycleState state = s.getState();
            if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                    && LifecycleState.DESTROYED.compareTo(state) >= 0) {
                // Nothing to do. stop() was already called
            } else {
                s.stop();
                s.destroy();
            }
        } catch (LifecycleException e) {
            log.error(sm.getString("catalina.stopError"), e);
        }

    }

Catalina 的 stop 方法主要邏輯是調用 Server 對象的 stop 方法。


小結Catalina 類承接了 Bootstrap 類的 load 和 start 方法,而後根據配置初始化了 Tomcat 的組件,並調用了 Server 類的 init 和 start 方法來啓動 Tomcat。

相關文章
相關標籤/搜索