Tomcat组件梳理--Catalina
Tomcat组件梳理--Catalina
1.定义和功能
Catalina是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑。通过Coyote模块提供连接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。结构如下:

主要的功能包括接收请求,处理请求,返回结果。但是这些具体的实现是在catalina里面的子容器里面,我们在对应的文章里面讲解,此处聚焦在Catalina的源代码提供的功能上。
处理上面这些,Catalina还提供启动入口,关闭入口等。
2.属性
//org.apache.catalina.startup.Catalina
/**
 * 用于await的flag
 */
protected boolean await = false;
/**
 * Server配置的文件路
 */
protected String configFile = "conf/server.xml";
/**
 * The shared extensions class loader for this server.
 * 此server的shared 类加载器
 */
protected ClassLoader parentClassLoader =
  Catalina.class.getClassLoader();
/**
 * Server组件
 */
protected Server server = null;
/**
 * 使用shutdown钩子的flag
 */
protected boolean useShutdownHook = true;
/**
 * Shutdown钩子实例
 */
protected Thread shutdownHook = null;
/**
 * 默认需要开启Naming
 */
protected boolean useNaming = true;
/**
 * 预防重复加载的标记字段
 */
protected boolean loaded = false;
有几个主要的属性。
- Catalina的子组件Server,通过digster工具解析server.xml文件构造该对象。
 - 用户shutdown时的钩子,是否使用以及调用的线程
 - 是否需要启动JNDI的标识
 - Server.xml配置文件的地址
 - stop用的await标记
 - 父类加载器。
 
3.操作
Catalina的操作有比较明显的区分,因为主要是处理来自shell的不同命令,所以,根据shell的传入的命令行,我们可以看到Catalina主要处理来自shell的start和stop命令。下面来解析start命令和stop命令的背后,以及Tomcat中提供的一个对xml解析很有用的库Digester。
再看一下Bootstrap类中的main方法中对shell命令的处理,可以看到start时主要调用load(args)和start()方法,stop时主要调用stopServer(args)方法。
//3.判断shell传入的值,执行对应的动作
if (command.equals("startd")) {
  //执行start方法的内容,主要为执行Catalina的load()和start()方法
  args[args.length - 1] = "start";
  daemon.load(args);
  daemon.start();
} else if (command.equals("stopd")) {
  args[args.length - 1] = "stop";
  daemon.stop();
} else if (command.equals("start")) {
  daemon.setAwait(true);
  daemon.load(args);
  daemon.start();
  if (null == daemon.getServer()) {
    System.exit(1);
  }
} else if (command.equals("stop")) {
  daemon.stopServer(args);
} else if (command.equals("configtest")) {
  daemon.load(args);
  if (null == daemon.getServer()) {
    System.exit(1);
  }
  System.exit(0);
} else {
  log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
3.1.执行shell的start命令
3.1.1.调用load(args)方法
现在我们知道Shell的start命令交给Catalina处理时,实际上调用了load(args)和start()方法。我们先看load(args)方法
//org.apache.catalina.startup.Catalina#load(java.lang.String[])
/*
 * 使用参数进行load
 */
public void load(String args[]) {
    try {
        //1.根据传入的参数设置Catalina的一些属性的值
        if (arguments(args)) {
          //2.调用无参的load()方法
            load();
        }
    } catch (Exception e) {
        e.printStackTrace(System.out);
    }
}
可以看到这里有两个业务逻辑:
- 1.根据传入的参数设置Catalina的一些属性,这些属性主要就是Naming等一些值。
 - 2.调用午餐的load()方法
 
再看load()方法,去掉里面的异常和环境检查,可以看到主要逻辑如下:
//org.apache.catalina.startup.Catalina#load()
/**
 * 准备好环境,解析好server.xml文件生成好对象。server对象也准备好,然后调用server的init方法
 */
public void load() {
    //1.检查java.io.tmpdir是有有效
    initDirs();
    //2.设置catalina.useNaming的系统参数
    initNaming();
    //3.用digester解析server.xml文件,把配置文件中的配置解析成java对象
    //3.1.准备好用来解析server.xml文件需要用的digester。
    Digester digester = createStartDigester();
    //3.2.server.xml文件作为一个输入流传入
    File file = configFile();
    InputStream inputStream = new FileInputStream(file);
    //3.3.使用inputStream构造一个sax的inputSource
    InputSource inputSource = new InputSource(file.toURI().toURL().toString());
    inputSource.setByteStream(inputStream);
    //3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用
    digester.push(this);
    //3.5.调用digester的parse()方法进行解析。
    digester.parse(inputSource);
    //4.为子组件Server设置一些值
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    //执行server的init方法,start方法的准备方法
    getServer().init();
}
分析一下主要逻辑
1.检查java.io.tmpdir是有有效,这个没啥好说的。
2.设置catalina.useNaming的系统参数,这个也没啥好多的,等主要流程梳理完,咱们做一个JDNI的源码解析。
3.用digester解析server.xml文件,把配置文件中的配置解析成java对象。该过程需要经过5个步骤才可以。
- 3.1.准备好用来解析server.xml文件需要用的digester。
 - 3.2.读取server.xml文件作为一个输入流。
 - 3.3.使用inputStream构造一个sax的inputSource,因为digester底层用的是sax去解析的。
 - 3.4.把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用,digester自带一个栈的结构。
 - 3.5.调用digester的parse()方法进行解析。前面几步都是在准备环境,这里才是正真的去解析了。
 
4.为子组件Server设置一些值,并调用server的init方法,start方法的准备方法。
这里面最值得说的应该就是第3步,通过Digester去解析xml文件,每遇到一个匹配的节点,都可以添加一个对应的事件。对Digester的操作都是一些普通的API操作,这里就不解释了,可以查看官方文档,或者查看博客,API挺简单的。
3.1.2.调用start()方法
start()方法主要作用就是调用Server的start()方法,并将一个shutdown的钩子加到JVM中。
主要的逻辑如下:
- 1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法
 - 2.注册一个shutdown的钩子
 - 3.等待处理如何停止的问题
 
比较有意思的是3,如何停止一个服务,这种方法比较有意思,不过我们放在Server组件中去讲,因为实现是放在Server中的。
//org.apache.catalina.startup.Catalina#start
public void start() {
    //1.执行Server的start()方法,如果执行失败,就调用Server的destroy()方法
    try {
        //执行server的start方法
        getServer().start();
    } catch (LifecycleException e) {
        try {
            //如果start失败,就调用server的destroy方法
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }
    //2.注册一个shutdown的钩子
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
    //3.等待处理如何停止的问题
    if (await) {
        await();
        stop();
    }
}
在第2步的逻辑中,new了一个类去注册,该类的主要处理逻辑如下:
protected class CatalinaShutdownHook extends Thread {
    @Override
    public void run() {
        try {
            if (getServer() != null) {
                //1.调用Catalina的stop()方法
                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();
            }
        }
    }
}
可以看到,只是去调用Catalina的stop()方法。不过这总调用方法也是比较奇特的,通过Catalina.this.stop()方法,不知道这种是不是可以跨线程调用一个类的实例,如果能,那将是一个很棒。
[补充]:Catalina.this方法是内部类调用外部类的方法。可以用来解决线程之间传递示例的问题。
3.2.执行shell的stop命令
shell的stop命令落在Catalina上只是去调用stopServer(args)方法,具体方法实现如下:
public void stopServer(String[] arguments) {
    //1.参数设置
    if (arguments != null) {
        arguments(arguments);
    }
    Server s = getServer();
    //2.如果Server存在,就调用Server的stop和destroy方法进行关闭,
    if (s == null) {
        // Create and execute our Digester
        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 {
        // Server object already present. Must be running as a service
        try {
            s.stop();
            s.destroy();
        } catch (LifecycleException e) {
            log.error("Catalina.stop: ", e);
        }
        return;
    }
    // 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送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 (Exception ce) {
            ce.printStackTrace();
        }
    } else {
        log.error(sm.getString("catalina.stopServer"));
        System.exit(1);
    }
}
主要的业务逻辑有三个:
- 1.参数设置
 - 2.如果Server存在,就调用Server的stop和destroy方法进行关闭
 - 3.如果Server不存在,就重新解析server.xml文件构造server,然后通过socket发送shutdown命令关闭
 
关闭Server的具体实现,我们放在Server组件中。
4.总结
我们根据用户的调用,梳理了Catalina对start和stop命令行的相应方法。其中Digester对xml文件的解析时值得注意的,停止Server的方式也比较有意思,但是我们放在Server中解析。
Tomcat组件梳理--Catalina的更多相关文章
- Tomcat组件梳理—Service组件
		
Tomcat组件梳理-Service组件 1.组件定义 Tomcat中只有一个Server,一个Server可以用多个Service,一个Service可以有多个Connector和一个Contain ...
 - Tomcat组件梳理—Digester的使用
		
Tomcat组件梳理-Digester的使用 再吐槽一下,本来以为可以不用再开一个篇章来梳理Digester了,但是发现在研究Service的创建时,还是对Digester的很多接口或者机制不熟悉,简 ...
 - Tomcat组件梳理--Server
		
Tomcat组件梳理--Server 1.Server组件的定义和功能概述 定义: Server组件用于描述一个启动的Tomcat实例,一个Tocmat被启动,在操作系统中占用一个进程号,提供web服 ...
 - 1.Tomcat组件梳理—Bootstrap启动器
		
Tomcat组件梳理-Bootstrap启动器 一开始是直接从Server开始做梳理的,但是发现有很多东西是从Catalina传输过来的,Catalina又是从Bootstrap启动的,所以还是回过头 ...
 - 【转】Tomcat组件生命周期管理
		
Tomcat组件生命周期管理 Tomcat中Server,Service,Connector,Engine,Host,Context,它们都实现了org.apache.catalina.Lifecyc ...
 - Tomcat 组件介绍
		
用了好长时间tomcat,但是其实自己只是反复听了这个名字,对Tomcat并不了解 1.Tomcat组件 Catalina Coyote Jasper Cluster 2.组件介绍 Tomcat Co ...
 - Tomcat系列(4)——Tomcat 组件及架构详细部分
		
核心部分 1. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...
 - Tomcat系列(3)——Tomcat 组件及架构核心部分 4类主要组件(顶层,连接器,容器,嵌套)
		
1.架构图 2. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...
 - Tomcat组件
		
Tomcat组件 tomcat常用组件 Tomcat的组织结构 Tomcat是一个基于组件的服务器,它的构成组件都是可配置的,其中最外层的给件是CATALINA SERVLET容器,其他的组件按照一定 ...
 
随机推荐
- JDBC Api详解
			
一.什么是JDBC JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Jav ...
 - 完全卸载Oracle(亲身体验完整版)
			
完全卸载Oracle 用Oracle自带的卸载程序不能从根本上卸载Oracle,从而为下次的安装留下隐患,那么怎么才能完全卸载Oracle呢? 那就是直接注册表清除,步骤如下: 开始->设置-& ...
 - Python数据分析学习(一)
			
转摘:https://segmentfault.com/a/1190000015440560 一.数据初探 首先导入要使用的科学计算包numpy,pandas,可视化matplotlib,seabor ...
 - 【spring源码学习】spring事务中的嵌套事务中的保存点相关知识
			
JDBC事务保存点(setSavepoint, releaseSavepoint )实例 以下是使用事务教程中描述的setSavepoint和回滚的代码示例. 此示例代码是基于前面章节中完成的环境和数 ...
 - pom.xml      activatedProperties     --spring.profiles.active=uat    对应
			
<profiles> <profile> <id>dev</id> <properties> <!-- 环境标识,需要与配置文件的名称 ...
 - 使用innobackupex进行mysql的差异备份还原和延迟复制
			
使用innobackupex进行mysql的差异备份还原和延迟复制 背景: 有同事执行update语句没有添加where条件,导致大量脏数据,需要将这张表恢复到前一天 数据库上有备份,每周一次完整备份 ...
 - debian8 vga 文本模式下出现闪屏
			
这种问题是因为 grub 里面关于 分辨率大小不对的问题. 在 debian 里面,在文件 /boot/grub/grub.cfg 里面可以添加 vga 参数配置. 如下: 在 kernel 启动参数 ...
 - 配置cisco设备记录用户命令
			
R1(config)# aaa new-model R1(config)# aaa accounting commands 0 default start-stop group local R1(co ...
 - ByteUtil
			
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOExceptio ...
 - OpenStack(一)——OpenStack的相关概念
			
(1).OpenStack概述 OpenStack是一个由NASA(美国国家航空航天局)和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目. OpenStack是 ...