Tomcat源码解析-高层之Catalina

2019-08-06  本文已影响0人  贪睡的企鹅

1 Catalina职责

2 Catalina运行流程

2.1 Bootstarp运行流程

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

核心方法功能

image
2.2 Catalina运行流程
image

3 实例化Catalina

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

4 加载Catalina

加载Catalina核心流程如下:


/**
 * 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的高层次的封装。

使用流程

/**
 * 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被停止。


/**
 * 是否需要阻塞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
上一篇下一篇

猜你喜欢

热点阅读