Tomcat源码解析系列(二)Catalina

前言

上篇文章说道TomcatBootstrap类,在 start 的时候,分别调用了 Bootstrap 类的

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

这三个方法,这三个方法都类似,都是通过反射调用 Catalina 类的同名方法,setAwait 方法比较简单,就是把 Catalina 的 await 属性设置为true。本篇文章着重看一下 load 方法,和 start 方法

1. load 方法

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

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. 第一段

先看第一段,第一段中的 initDirs() 方法和 initNaming() 方法比较简单,可以忽略。然后是

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

这一行代码代码的作用是设置Tomcat要加载的配置的配置源,也就是 conf 目录下的 server.xml 文件。

然后

Digester digester = createStartDigester();

这一行创建了一个 Digester 对象,这个对象是用来解析 server.xml 文件的

/**
     * 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 类就不多讲了,有兴趣的话可以自行研究一下。


1.1.2. 第二段

有了这个 Digester 对象,就可以解析出 server.xml 里写的各种 xml 标签了,第一个 try-catch 就是调用 digester.parse 方法来解析 server.xml,具体的解析过程就不往下跟了,其实就是 用 SAXParser 来解析 xml,解析完了之后,xml 里定义的各种标签就有对应的实现类对象了。

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 方法。Server 类的 stop 方法

小结

Catalina 类承接了 Bootstrap 类的 load 和 start 方法,然后根据配置初始化了 Tomcat 的组件,并滴啊用了 Server 类的 init 和 start 方法来启动 Tomcat。

原文 

https://segmentfault.com/a/1190000022012525

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Tomcat源码解析系列(二)Catalina

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址