Tomcat源码解析(二): Bootstrap和Catalina

news2025/2/22 8:51:12

Tomcat源码系列文章

Tomcat源码解析(一): Tomcat整体架构

Tomcat源码解析(二): Bootstrap和Catalina


目录

  • 一、基础组件
    • 1、Lifecycle生命周期顶级接口
    • 2、组件的默认实现
  • 二、启动类Bootstrap
    • 1、main
    • 2、init
    • 3、load与start
  • 三、加载Catalina
    • 1、load
    • 2、start
      • 2.1、注册shutdown钩子
      • 2.2、监听shutdown命令
      • 2.3、停止Tomcat
  • 四、总结

一、基础组件

1、Lifecycle生命周期顶级接口

  • 由于所有的组件均存在初始化启动停止生命周期方法,拥有生命周期管理的特性
  • 基于生命周期管理抽象成了一个接口Lifecycle
  • 组件Server、Service、Container、Executor、Connector组件,都实现生命周期的接口

在这里插入图片描述

2、组件的默认实现

先回顾下组件的作用以及之间的关系

  • Server:表示整个Tomcat Catalina servlet容器,Server中可以有多个Service
  • Service:表示Connector和Engine的组合,对外提供服务,Service可以包含多个Connector和一个Engine
  • Connector:为Tomcat Engine的连接组件,支持三种协议:HTTP/1.1、HTTP/2.0、AJP
    • Endpoint负责提供字节流给Processor
    • Processor负责提供Tomcat Request对象给Adapter
    • Adapter负责提供ServletRequest对象给容器,实现类只有CoyoteAdapter
  • Engine:顶级容器,不能被其他容器包含,它接受处理连接器的所有请求,并将响应返回相应的连接器
  • Host:表示一个虚拟主机,包含主机名称和IP地址,这里默认是localhost
  • Context:表示一个 Web 应用程序,是 Servlet、Filter 的父容器
  • Wrapper:表示一个 Servlet,它负责管理 Servlet 的生命周期,并提供了方便的机制使用拦截器

在这里插入图片描述

组件默认实现的类图

  • 对于Endpoint组件来说,在Tomcat中没有对应的Endpoint接口, 但是有一个抽象类AbstractEndpoint
  • Tomcat8.5版本中,默认采用的是NioEndpoint

在这里插入图片描述

  • ProtocolHandler:通过封装Endpoint和Processor , 实现针对具体协议的处理功能
  • Tomcat按照协议和IO提供了6个实现类
    • AJP协议
      • AjpNioProtocol :采用NIO的IO模型
      • AjpNio2Protocol:采用NIO2的IO模型
      • AjpAprProtocol :采用APR的IO模型,需要依赖于APR库
    • HTTP协议
      • Http11NioProtocol :采用NIO的IO模型,默认使用的协议
      • Http11Nio2Protocol:采用NIO2的IO模型
      • Http11AprProtocol :采用APR的IO模型,需要依赖于APR库

在这里插入图片描述

二、启动类Bootstrap

  • 首先来看下整个启动过程,我们可以看到Bootstrap作为启动入口首先进行了初始化方法init然后load方法加载了Catalina

在这里插入图片描述

1、main

  • Bootstrap的main方法首先会创建一个Bootstrap对象,调用它的init方法初始化
  • 然后根据启动参数,调用Bootstrap对象的不同方法,默认模式为start,该模式下将会先后调用loadstart方法
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;

// Bootstrap类的main方法
public static void main(String args[]) {
    // 创建一个 Bootstrap 对象
    synchronized (daemonLock) {
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 调用init方法初始化
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }
    }

    // 根据启动参数,分别调用 Bootstrap 对象的不同方法
    try {
        // 默认参数为start
        String command = "start"; 
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            ...
        } else if (command.equals("stopd")) {
            ...
        } 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")) {
           ...
        } else if (command.equals("configtest")) {
            ...                    
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        if (t instanceof InvocationTargetException &&
                t.getCause() != null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1);
    }
}

2、init

  • 本文对类加载器内容不做分析,后续看情况单独讲
  • 简单来说init就是反射实例化Catalina对象
public void init() throws Exception {
	// 初始化类加载器相关内容
    initClassLoaders();
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
        
    // 通过catalinaLoader加载Catalina,反射实例化Catalina对象
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
        
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
   // 反射将sharedLoader设置为catalinaLoader的父类加载器,本文不做分析
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
	
	// 将catalina实例引用赋值
    catalinaDaemon = startupInstance;
}

3、load与start

  • load与start都是通过上一步获取到的catalinaDaemon对象反射调用catalina类的loadstart方法
  • 这两个过程我们会在下面的Catalina内容中介绍

load方法:

private void load(String[] arguments) throws Exception {
    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); 
    // 反射调用catalina的load方法,参数为null
    method.invoke(catalinaDaemon, param);
}

start方法:

 public void start()
     throws Exception {
     if( catalinaDaemon==null ) init();
	 // 反射调用catalina的start方法,参数为null
     Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
     method.invoke(catalinaDaemon, (Object [])null);
 }

三、加载Catalina

  • 上文中Bootstrap类的load与start方法实质上就是反射调用catalina类的load与start方法

1、load

  • 创建Digester对象,解析conf/server.xml文件
  • 调用Server实现类StandardServerinit方法来初始化组件(下篇文章单独讲)
public void load() {
	// 如果已经加载则退出,默认false,下面会置为true
    if (loaded) {
        return;
    }
    loaded = true;

    initDirs();
    initNaming();

    // 创建Digester对象,用来解析server.xml文件
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;

     try {
     	 // 加载conf目录下的server.xml文件
         file = configFile();
         inputStream = new FileInputStream(file);
         inputSource = new InputSource(file.toURI().toURL().toString());
     } catch (Exception e) {
		...
     }

     try {
         inputSource.setByteStream(inputStream);
         digester.push(this);
         // 开始解析conf/server.xml文件
         digester.parse(inputSource);
     } catch (SAXParseException spe) {
		...
     } 

	// server和catalina之间建立关联
	// Server接口实现类StandardServer是在解析server.xml文件时候创建
	// 当时StandardServer对象set到Catalina
	// 此时又将Catalinaset到StandardServer对象中
	// 形成:你中有我,我中有你
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

	...

    // 初始化server,后面另开一篇单独讲
    try {
        getServer().init();
    } catch (LifecycleException e) {
		...
    }
}

Digester对象解析server.xml文件

在这里插入图片描述

2、start

  • 再来看下整个启动过程
  • Catalina的load方法最后一步getServer().init(),就是Server、Service、Engine等一系列组件的初始化

在这里插入图片描述

  • 调用server实现类StandardServerstart方法来启动服务器(下篇文章单独讲)
public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        // 无法启动服务器。未配置服务器实例
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    long t1 = System.nanoTime();

    // 调用server的start方法来启动服务器
    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("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    // 注册关闭钩子
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        // 注册shutdown钩子,main结束时调用
        // 如果server未停止调用stop方法停止
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }
	// 进入等待状态
	// 启动类Bootstrap默认调start方法设置await=true
    if (await) {
    	// main线程等待,等待接收shutdown命令,接受到则跳出阻塞
        await();
        // 跳出阻塞,执行Server.stop();
        stop();
    }
}

2.1、注册shutdown钩子

  • 注册shutdown钩子(CatalinaShutdownHook),即注册一个线程任务main结束时调用
  • getServer() != null 如果server未停止调用Catalina的stop方法停止
protected class CatalinaShutdownHook extends Thread {
    @Override
    public void run() {
        try {
            if (getServer() != null) {
                Catalina.this.stop();
            }
        } catch (Throwable ex) {
			...
        }
    }
}

2.2、监听shutdown命令

  • Socket监听8005端口shutdown命令
  • 服务启动后服务器会监听8005端口,如果这个端口接收到了"SHUTDOWN"这个字符串,那么就会终止Server

server.xml开头内容

在这里插入图片描述

  • Catalina的await方法实际是调用Server实现类StandardServer的await方法
public void await() {
    getServer().await();
}
  • while循环监听Socket8005端口
  • 如果Socket输入流读取到字符串“SHUTDOWN”,跳出while循环
  • Catalina的await阻塞方法就通过了
// StandardServer类方法

private int port = 8005;

private String shutdown = "SHUTDOWN";

private volatile ServerSocket awaitSocket = null;

@Override
public void await() {
    // shutdown端口配置为-2,启动完Server直接再终止Server
    if( port == -2 ) {
        return;
    }
    // 配置为-1,则不再监听shutdown端口
    if( port==-1 ) {
        try {
            awaitThread = Thread.currentThread();
            while(!stopAwait) {
                try {
                    Thread.sleep( 10000 );
                } catch( InterruptedException ex ) {
                    // continue and check the flag
                }
            }
        } finally {
            awaitThread = null;
        }
        return;
    }

    // 开启socket监听server.xml中的shutdown端口
    // 创建socket服务端
    try {
        awaitSocket = new ServerSocket(port, 1,
                InetAddress.getByName(address));
    } catch (IOException e) {
        return;
    }
    
    // 默认false,进入while循环
    while (!stopAwait) {
        ServerSocket serverSocket = awaitSocket;
        if (serverSocket == null) {
            break;
        }

        // Wait for the next connection
        Socket socket = null;
        StringBuilder command = new StringBuilder();
        InputStream stream;
        try {
        	// accept阻塞监听端口
            socket = serverSocket.accept();
            // 设置阻塞超时时间10秒,如果超时抛异常,catch捕捉到重新进入while循环
            socket.setSoTimeout(10 * 1000);  
            stream = socket.getInputStream();
        } catch (SocketTimeoutException ste) {
            continue;
        }
		
		// 从流中读取字符串
		...
                
        // 如果读取到字符串命令是"SHUTDOWN"则,跳出循环,开始终止服务器
        // shutdown变量是取server.xml中Server的shutdown属性
        boolean match = command.toString().equals(shutdown);
        if (match) {
            log.info(sm.getString("standardServer.shutdownViaPort"));
            break;
        } else
            log.warn("StandardServer.await: Invalid command '"
                    + command.toString() + "' received");
    }
}

2.3、停止Tomcat

  • await方法的作用是停住主线程,等待用户输入SHUTDOWN命令之后
    • 停止等待,然后main线程就调用stop方法停止Tomcat
  • 最终调用Server的stop和destroy方法(下篇文章单独讲)
public void stop() {
    try {
        if (useShutdownHook) {
        	// 移除shutdown钩子,这个stop方法会停止server,不需要钩子再次执行
            Runtime.getRuntime().removeShutdownHook(shutdownHook);
            
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        true);
            }
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }

    // 调用Server的stop和destroy方法
    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("Catalina.stop", e);
    }
}

四、总结

  • Bootstrap是一个启动引导类,本身没有太多启动关闭细节的实现
  • 而是通过加载Catalina,对Catalina发号施令,调用start、stop等方法

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1478187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024腾讯云优惠券免费领取_代金券查询和使用方法

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

工厂模式 详解 设计模式

工厂模式 其主要目的是封装对象的创建过程&#xff0c;使客户端代码和具体的对象实现解耦。这样子就不用每次都new对象&#xff0c;更换对象的话&#xff0c;所有new对象的地方也要修改&#xff0c;违背了开闭原则&#xff08;对扩展开放&#xff0c;对修改关闭&#xff09;。…

spring6学习笔记

1.环境准备 1.idea建立一个空项目&#xff0c;jdk要求是17 2.Maven配置&#xff08;和mybatis里一样&#xff09; 3.新建一个模块 2.ocp原则 3.依赖倒置原则&#xff08;DIP&#xff09; 什么是依赖倒置原则? 1.面向接口编程&#xff0c;面向抽象编程&#xff0c;不要面向…

Windows安装VNC连接工具并结合cpolar实现远程内网Ubuntu系统桌面

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

回溯【基础算法精讲 14】

视频地址 : 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 基本概念 1 . 例子 例如从abc和def(n 2)中各选出一个组成新的字符串? 如果n很大 , 这个时候for循环的表达能力有限 ; 2 . 原问题 和 子问题 3 . 增量构造答案 这个增量构造答案的过程就是回溯…

java之Bean对象

1. 什么是Bean&#xff1f; Bean被实例化的&#xff0c;是被Spring框架所管理的Java对象。 Spring容器会自动完成Bean的实例化。将所创建的的Bean自动注入到Ioc容器中以供调用。 spring框架中 IOC容器中管理的对象就是Bean对象 2. 第三方bean Bean 因为第三方bean&#xff0…

SQL函数学习记录

聚合函数 函数是编程语言的基础之一&#xff0c;在对数字的运算中&#xff0c;我们用的最多的就是聚合函数&#xff0c;本篇接下来就详细阐述下SQL中聚合函数的运用。 什么是聚合函数&#xff08;aggregate function&#xff09;&#xff1f; 聚合函数指的是对一组值执行计算…

手撕LRU缓存——LinkedHashMap简易源码

题目链接&#xff1a;https://leetcode.cn/problems/lru-cache/description/?envTypestudy-plan-v2&envIdtop-100-liked 原理非常简单&#xff0c;一个双端链表配上一个hash表。 首先我们要知道什么是LRU就是最小使用淘汰。怎么淘汰&#xff0c;链表尾部就是最不常用的直接…

92、评估代码生成操作带来的性能提升

本节评估一下,通过代码生成操作之后,对于模型的性能提升。 评估下性能 在相同的环境下,分别运行 4th_no_malloc 和 5th_codegen 下的 compile.sh 脚本进行代码编译,然后运行编译后生成的可执行文件 ./resnet。 可以分别获取到权值预加载前后的性能指标。 注意:不同电脑机…

可视化图文报表

Apache Echarts介绍 Apache Echarts是一款基于Javascript的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表。 官网&#xff1a;Apache ECharts 入门案例&#xff1a; <!DOCTYPE html> <html>…

Git教程-Git的基本使用

Git是一个强大的分布式版本控制系统&#xff0c;它不仅用于跟踪代码的变化&#xff0c;还能够协调多个开发者之间的工作。在软件开发过程中&#xff0c;Git被广泛应用于协作开发、版本管理和代码追踪等方面。以下是一个详细的Git教程&#xff0c;我们将深入探讨Git的基本概念和…

基于大模型思维链(Chain-of-Thought)技术的定制化思维链提示和定向刺激提示的心理咨询场景定向ai智能应用

本篇为个人笔记 记录基于大模型思维链&#xff08;Chain-of-Thought&#xff09;技术的定制化思维链提示和定向刺激提示的心理咨询场景定向ai智能应用 人工智能为个人兴趣领域 业余研究 如有错漏欢迎指出&#xff01;&#xff01;&#xff01; 目录 本篇为个人笔记 记录基…

【算法】最小生成树—Prim算法与Kruskal算法

Prim算法和Kruskal算法都是解决最小生成树问题的经典算法。最小生成树是原图的最小连通子图&#xff0c;它包含原图的全部结点&#xff0c;且保持图连通的所有边代价和最小。一个连通图可能有多个最小生成树。 一、Prim算法 含义 Prim算法&#xff0c;也被称为普里姆算法&…

项目解决方案: 实时视频拼接方案介绍

目 录 1、实时视频拼接概述 2、适用场景 3、系统介绍 3.1拼接形式 3.1.1横向拼接 3.1.2纵向拼接 3.2前端选择 3.2.1前端类型 3.2.2推荐配置 3.3后端选择 3.3.1录像回放 3.3.2客户端展示 4、拼接方案介绍 4.1基于4K摄像机的拼接方案 4.1.1系统架构…

ESP8266智能家居(3)——单片机数据发送到mqtt服务器

1.主要思想 前期已学习如何用ESP8266连接WIFI&#xff0c;并发送数据到服务器。现在只需要在单片机与nodeMCU之间建立起串口通信&#xff0c;这样单片机就可以将传感器测到的数据&#xff1a;光照&#xff0c;温度&#xff0c;湿度等等传递给8266了&#xff0c;然后8266再对数据…

typescript 的常用方式

文章目录 前言一、绑定props 默认值的方式&#xff1a;withDefaults1.vue2 的props设置默认值2.vue3 的props设置默认值(1) 不设置默认值的写法(2) 设置默认值的写法&#xff08;分离模式&#xff09;(3) 设置默认值的写法&#xff08;组合模式&#xff09; 二、定义一个二维数…

Qt|QTreewidget类下函数qt助手详解说明示例(上)

该系列持续更新&#xff0c;喜欢请一键三连&#xff0c;感谢各位大佬。 QT5.14.2 参考官方QT助手 文章目录 QTreeWidget ClasspropertiesPublic Functions默认构造函数默认析构函数添加根节点void addTopLevelItem(QTreeWidgetItem *item)添加多个根节点void addTopLevelItems…

Linux Shell脚本练习(一)

一、 Linux下执行Shell脚本的方式&#xff1a; 1、用shell程序执行脚本&#xff1a; a、根据你的shell脚本的类型&#xff0c;选择shell程序&#xff0c;常用的有sh&#xff0c;bash&#xff0c;tcsh等 b、程序的第一行#!/bin/bash里面指明了shell类型的&#xff0c;比如#!/…

linux查看服务器内核CUP版本相关命令

服务器参考 计算架构&#xff1a;x86-64产品系列&#xff1a;华为云耀云服务器操作系列&#xff1a;CentOS 7 执行uname -a查看服务器内核版本 Linux hecs-82210 3.10.0-1160.92.1.el7.x86_64 #1 SMP Tue Jun 20 11:48:01 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux执行hostn…

Aws Ec2服务器设置密码登录

通过密钥&#xff0c;ssh登录到服务器 切换到root sudo -i开始设置root的新密码 passwd root输入并确认新密码即可 5.修改ssh配置文件 vim /etc/ssh/sshd_config6.重启sshd配置 systemctl restart sshd