Tomcat源码解析-高层之Catalina

1 Catalina职责

  • 通过解析server.xml、构建tomcat内所有组件。
  • 给tomcat JVM进程注册一个钩子线程,负责用来当tomcat被关闭时完成一些清理工作。

2 Catalina运行流程

2.1 Bootstarp运行流程

Bootstarp作为tomcat启动类,JVM启动会调用main函数完成tomcat启动启动,停止,配置等功能,其内部流程如下:

  • 1 实例化Bootstrap对象。

  • 2 对Bootstrap对象init,其内部包括构建Tomcat自定义类加载器,并实例化Catalina对象

  • 3 以main函数args参数作为指令,通过调用Catalina类中不同的方法,完成相应的功能。

    • startd指令 : 首先调用load()方法,接着调用start()方法,
    • start指令 : 首先调用setAwait()方法,接着调用load()方法,最后调用start()方法
    • stopd指令 : 调用stopServer()方法
    • configtest指令 : 调用load()方法

核心方法功能

  • bootstrap.init():负责初始化Bootstrap,在初始化的过程最重要的就是创建tomcat自定义的类加载器commonLoader,catalinaLoader,sharedLoader,并创建Catalina。
  • daemon.load(args):负责通过反射调用Catalina.load方法,实现tomcat加载。
  • daemon.start():负责通过反射调用Catalina.start方法,实现tomcat启动。
  • daemon.stop():负责通过反射调用Catalina.stop方法,实现tomcat停止。
  • daemon.setAwait(true):负责通过反射调用Catalina.setAwait方法,设置当前命令执行执行的线程是否要阻塞,对于start指令,当前线程是tomcat主线程,我们当然需要阻塞主线程,保证tomcat的运行。而主线程被释放则表示tomcat被停止。
image
2.2 Catalina运行流程
  • catalina.init():使用Digester解析Server.xml,并构建Server.xml中描述的所有组件,之后调用刚刚构建好的Server组件初始化方法init().
  • catalina.start():首先调用Server组件start启动方法,之后给当前JVM进程注册一个钩子线程,该线程负责用来当tomcat被关闭时完成一些清理工作。最后调用Server组件await()方法阻塞当前线程(tomcat主线程),如果当前线程从await()方法返回表示tomcat被停止。
  • catalina.stop():负责向JVM清理掉注册的钩子线程,之后调用Server组件停止方法stop(),最后调用清理方法destroy()。
  • catalina.stopServer():首先调用Server组件停止方法stop(),之后调用清理方法destroy(),最后向Tomcat指定的端口发送shutdown指令,tomcat接受指令后会使tomcat主线程从阻塞中退出。
image

3 实例化Catalina

 /**
 * 实例化Catalina
 */
public Catalina() {
    /** 从catalina.properties读取受保护类,注册到Security中  **/
    setSecurityProtection();
    ExceptionUtils.preload();
}

4 加载Catalina

加载Catalina核心流程如下:

  • 1 Catalina加载核心是使用Digester解析Server.xml,并实例化所有组件

  • 2 调用Server组件初始化方法init(),由于server组件是所有tomcat组件父组件,而父组件初始化过程中会调用子组件的初始化,因此可以说调用Server组件start初始化方法相当于一键初始化tomcat中所有组件;


/**
 * tomcat配置文件,用来实例化Tomcat Server组件
 */
protected String configFile = "conf/server.xml";

/**
 * Tomcat Server组件
 */
protected Server server = null;


public void load() {

        /** 判断是否已经加载过,防止重复加载 **/
        if (loaded) {
            return;
        }
        loaded = true;

        long t1 = System.nanoTime();

        /** 检查java.io.tmdir系统属性值对应目录是否存在  **/
        initDirs();

        /** 初始化JNDI系统属性 **/
        initNaming();

        /** 创建Digester实例,Digester中定义解析/conf/Server.xml文件规则**/
        Digester digester = createStartDigester();

            InputSource inputSource = null;
            InputStream inputStream = null;
            File file = null;
            try {
            /** 读取 catalina_home\conf\server.xml 配置文件 **/
            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);
                }
            }
            /** 读取 classpath:\conf\server.xml **/
            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);
                    }
                }
            }

            /** 读取 classpath:\conf\server-embed.xml **/
            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;
            }

            /** 解析xml转换为server对象,并设置到Catalina.server属性中 **/
            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
                }
            }
        }

        /** 设置server组件对应catalina启动类对象  **/
        getServer().setCatalina(this);
        /** 设置server组件在所在tomcat安装目录  **/
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        /** 设置server组件在所在tomcat工作目录  **/
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

        // 调用System类的方法重定向标准输出和标准错误输出
        initStreams();

        /** 初始化Server【一键初始化tomcat所有组件】**/
        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");
        }
    }

    /**
     * 返回配置文件的绝对路径File对象
     * @return the main configuration file
     */
    protected File configFile() {

        File file = new File(configFile);
        /** 判断路径名是否是绝对的 **/
        if (!file.isAbsolute()) {
            file = new File(Bootstrap.getCatalinaBase(), configFile);
        }
        return (file);

    }    
4.1 Digester

Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。

使用流程

  • 1 是创建一个解析器Digester,其中定义每一个标签对应的一个或多个解析规则。

  • 2 将当前对象Catalina压入Digester栈结构顶部,作为要处理对象。

  • 3 将xml文件解析成对象,并按照标签名称设置到栈顶元素对应的属性中。这里会将XML文件解析为一个Server组件对象设置到Catalina对象server属性中。

/**
 * Tomcat Server组件
 */
protected Server server = null;


/** 将xml解析对象放置到当前对象属性中 **/
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;
}

默认server.xml

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" socket.soKeepAlive="true"/>
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

详细请参考:Tomcat相关技术-Digester(二)Digester使用和原理

5 启动Catalina

启动Catalina核心流程如下:

1 启动Catalina核心是调用Server组件start启动方法。由于server组件是所有tomcat组件父组件,而父组件启动过程中会调用子组件的启动,因此可以说调用Server组件start启动方法相当于一键启动tomcat中所有组件;

2 给当前JVM进程注册一个钩子线程,用来完成tomcat进程被关闭前执行一些清理工作。

3 调用Server组件await()方法阻塞当前线程(tomcat主线程)。如果当前线程从await()方法返回表示tomcat被停止。

如下情况会导致tomcat被停止。

  • 1 内部调用server组件stopAwait()方法,表示将要停止tomcat
  • 2 接收到客户端发起SHUTDOWN命令

/**
 * 是否需要阻塞tomcat主线程
 */
protected boolean await = false;

 /**
 * 是否需要向JVM注册关闭钩子线程,当JVM发生以下情况关闭前,会触发注册钩子线程动作
 * 1. 程序正常退出
 * 2. 使用System.exit()
 * 3. 终端使用Ctrl+C触发的中断
 * 4. 系统关闭
 * 5. 使用Kill pid命令干掉进程
 */
protected boolean useShutdownHook = true;


/**
 * JVM注册关闭钩子线程
 */
protected Thread shutdownHook = null;


/**
 * 是否开启JNDI服务
 */
protected boolean useNaming = true;


/**
 * 加载标识,防止重复加载。
 */
protected boolean loaded = false;


/**
 * 启动Catalina
 */
public void start() {

    /** 如果不存在server组件,说明初始化失败,重新加载Catalina **/
    if (getServer() == null) {
        /** 重新加载Catalina **/
        load();
    }

    /** 如果还是找不到server组件,直接返回 **/
    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    /** 获取当前时间 **/
    long t1 = System.nanoTime();

    /** 启动Server组件 **/
    try {
        getServer().start();
    }
    /** 发生LifecycleException异常 销毁Server组件 **/
    catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            /** 销毁Server组件 **/
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    /** 打印tomcat执行启动用时 **/
    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    /**  判断是否需要向JVM注册一个钩子线程, **/
    if (useShutdownHook) {
        /** 实例化钩子线程CatalinaShutdownHook **/
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        /**将shutdownHook注册JMV**/
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }
    /** 执行Bootstarp.main函数的指令为start时会设置 Catalina的await属性为true,**/
    if (await) {
        /**
         * 阻塞当前线程,当前线程从await()方法返回表示tomcat被停止
         *
         * 如下两种情况会导致tomcat停止
         *
         * 1 内部调用server组件stopAwait()方法
         *
         * 2 接收到客户端发起SHUTDOWN命令
         * **/
        await();
        /** 停止Catalina **/
        stop();
    }
}

public void await() {
    getServer().await();
}

/**
 * tomcat关闭注册到JVM中钩子线程
 */
protected class CatalinaShutdownHook extends Thread {

    @Override
    public void run() {
        try {
            /** 如果tomcat server组件存在则调用Catalina.this.stop() **/
            if (getServer() != null) {
                Catalina.this.stop();
            }
        } catch (Throwable ex) {
            ExceptionUtils.handleThrowable(ex);
            log.error(sm.getString("catalina.shutdownHookFail"), ex);
        } finally {
            /**  关闭ClassLoaderLogManager **/
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).shutdown();
            }
        }
    }
}

6 停止Catalina

停止Catalina核心流程如下:

1 向JVM清理掉注册的钩子线程

2 调用Server组件停止方法stop()以及清理方法destroy()

/**
 * 停止Catalina
 */
public void stop() {

    try {
        /** 判断是否向JVM注册了钩子线程 **/
        if (useShutdownHook) {
            /**  向JVM清理掉注册的钩子线程 **/
            Runtime.getRuntime().removeShutdownHook(shutdownHook);

            /**设置LogManager中注册到JVM钩子线程在JVM停止时会执行,重置日志系统 **/
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        true);
            }
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }

    /** 获取Tomcat Server组件,调用stop()关闭,destroy()清理 **/
    try {
        Server s = getServer();
        LifecycleState state = s.getState();
        /** 如果Tomcat Server组件 状态为'STOPPING_PREP', 'DESTROYED' 忽略当前动作**/
        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("Catalina.stop", e);
    }

}

7 stopServer

1 创建一个用来处理停止Server解析器digester。使用新建的digester重新解析设置到当前对象Catalina,Server属性中。这里仅仅时为了清理tomcat中组件对象占用的堆内存。

2 调用Server组件停止方法stop()以及清理方法destroy()

3 向Tomcat Server指定的端口发送shutdown指令,停止tomcat主线程。

    public void stopServer(String[] arguments) {

        /** 处理指定的命令行参数 **/
        if (arguments != null) {
            arguments(arguments);
        }

        /** 获取tomcat Server组件实例 **/
        Server s = getServer();
        /** 如果omcat Server组件实例不存在,使用Digester构造一个tomcat Server组件 **/
        if (s == null) {
            Digester digester = createStopDigester();
            File file = configFile();
            try (FileInputStream fis = new FileInputStream(file)) {
                InputSource is =
                    new InputSource(file.toURI().toURL().toString());
                is.setByteStream(fis);
                digester.push(this);
                digester.parse(is);
            } catch (Exception e) {
                log.error("Catalina.stop: ", e);
                System.exit(1);
            }
        } else {
            /** 对存在Tomcat Server组件执行停止,销毁动作 **/
            try {
                s.stop();
                s.destroy();
            } catch (LifecycleException e) {
                log.error("Catalina.stop: ", e);
            }
            return;
        }

        /**
         * 向Tomcat Server指定的端口发送shutdown指令
         */
        s = getServer();
        if (s.getPort()>0) {
            try (Socket socket = new Socket(s.getAddress(), s.getPort());
                    OutputStream stream = socket.getOutputStream()) {
                String shutdown = s.getShutdown();
                for (int i = 0; i < shutdown.length(); i++) {
                    stream.write(shutdown.charAt(i));
                }
                stream.flush();
            } catch (ConnectException ce) {
                log.error(sm.getString("catalina.stopServer.connectException",
                                       s.getAddress(),
                                       String.valueOf(s.getPort())));
                log.error("Catalina.stop: ", ce);
                System.exit(1);
            } catch (IOException e) {
                log.error("Catalina.stop: ", e);
                System.exit(1);
            }
        } else {
            log.error(sm.getString("catalina.stopServer"));
            System.exit(1);
        }
    }

8 总结时刻

Catalina是tomcat的启动类,负责实现Bootstrap指令调用方法,其中主要包括start,load,setAwait,stopServer。而其核心功能是在load方法中通过解析server.xml、构建tomcat内所有组件,并通过构建tomcat最高层组件server来实现tomcat一键初始化,启动,停止。

Catalina会在start方法给tomcat JVM进程注册一个钩子线程,负责用来当tomcat被关闭时完成一些清理工作,并调用Server组件await()方法阻塞tomcat主线程.

image
image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容