美文网首页
Tomcat源码解析-高层之Catalina

Tomcat源码解析-高层之Catalina

作者: 贪睡的企鹅 | 来源:发表于2019-08-06 16:48 被阅读0次

    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

    相关文章

      网友评论

          本文标题:Tomcat源码解析-高层之Catalina

          本文链接:https://www.haomeiwen.com/subject/byewdctx.html