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

Tomcat源码系列文章

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

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


目录

  • 前言
  • 一、启动类Bootstrap
    • 1、main
    • 2、init
    • 3、load与start
  • 二、加载Catalina
    • 1、load
    • 2、start
      • 2.1、注册shutdown钩子
      • 2.2、监听shutdown命令
      • 2.3、停止Tomcat
  • 三、总结

前言

  • 在tomcat的bin目录下有两个启动tomcat的文件
    • 一个是startup.bat,它用于windows环境下启动tomcat
    • 另一个是startup.sh,它用于linux环境下启动tomcat
  • 两个文件中的逻辑是一样的, 我们只分析其中的startup.bat
  • 而startup.bat文件实际上就做了一件事情:启动catalina.bat
  • catalina.bat中下面这段指定了tomcat的启动类为Bootstrap这个类,catalina.bat最终执行了Bootstrap类中的main方法来启动tomcat
set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start

一、启动类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等方法

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

2.协议设计和解析 协议 在计算机中&#xff0c;协议是指一组规则和约定&#xff0c;用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法&#xff0c;以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层…

【PCL】 (十六)点云距离图可视化

&#xff08;十六&#xff09;点云距离图可视化 以下代码实现点云及其对应距离图的可视化。 数据样例&#xff1a;sphere100.pcd range_image_visualization.cpp #include <iostream>#include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h&g…

C++11常用知识分享(二)【可变参数模板 || lambda表达式 || 包装器】

目录 一&#xff0c;可变参数模板 1. 递归方法展开参数包 2. 逗号表达式展开参数包 3&#xff0c;可变参数模板优势 二&#xff0c;lambda表达式 1. lambda表达式语法 2. 注意点 三&#xff0c;包装器 1. bind(了解) 嗨&#xff01;收到一张超美的风景图&#xff0c;希…

C向C++的一个过渡

思维导图 输入输出&#xff0c;以及基础头文件 在c语言中我们常用scanf("%d",&n);和printf("%d\n",n);来输出一些变量和常量&#xff0c;在C中我们可以用cin;和cout;来表示输入输出。 在C语言中输入输出有头文件&#xff0c;在C也有头文件&#xff0…

#WEB前端(CCS选择器)

1.实验&#xff1a;CCS选择器 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; 子代选择器、后代选择器、相邻兄弟选择器、类选择器、伪元素选择器&#xff08;鼠标悬停&#xff09;、ID选择器、调用选择器&#xff08;全选&#xff09; 4.代码&#xff1a; <!DOCTYPE html…

Vue.js 实用技巧:深入理解 Vue.set 方法

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

JMeter常用函数整理

"_csvRead"函数 csvRead函数是从外部读取参数&#xff0c;csvRead函数可以从一个文件中读取多个参数。 下面具体讲一下如何使用csvread函数&#xff1a; 1.新建一个csv或者text文件&#xff0c;里面保存要读取的参数&#xff0c;每个参数间用逗号相隔。每行表示每一组…

MATLAB:Image Processing Toolbox工具箱入门实战

目录 1.基本图像导入、处理和导出 2.实战项目一&#xff1a;利用imfindcircles()函数检测和测量图像中的圆形目标 1.基本图像导入、处理和导出 Basic Image Import, Processing, and Export- MATLAB & SimulinkThis example shows how to read an image into the worksp…

BUUCTF---[极客大挑战 2019]Http1

1.题目描述&#xff0c;在地址框输入下面的网址 2.来到页面&#xff0c;ctrlu查看源码&#xff0c;仔细观察会看到一个.php的跳转页面 3.点进去页面提示It doesnt come from https://Sycsecret.buuoj.cn 4.页面提示它不是来源于这个网址&#xff0c;我们需要用bp抓包对数据进行…

从0到1全流程使用 segment-anything

从0到1全流程使用 segment-anything 一、安装 anaconda 一、下载 anaconda 二、以管理员身份运行安装 1、勾选 Just Me 2、统一安装路径(后续 python 等包也安装至此目录) 3、勾选 add to path 然后安装即可。 三、修改 Anaconda 默认路径及默认缓存路径 Anaconda 默认下…

神经网络3-时间卷积神经网络

在深度学习的知识宝库中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;广泛应用于视觉&#xff0c;视频等二维或者多维的图像领域。卷积网络具有深度&#xff0c;可并行等多种优良特性&#xff0c;那么这种技术是否可以应用于解单维度的时间序列问题呢&#xff1f;本文介…

基于Springboot的助农管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的助农管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

UTONMOS元宇宙游戏发展趋势是什么?

UTONMOS元宇宙游戏的发展趋势包括以下几个方面&#xff1a; 更加真实的体验&#xff1a;随着技术的进步&#xff0c;UTONMOS元宇宙游戏将提供更加逼真的视觉、听觉和触觉体验&#xff0c;让玩家更加身临其境。 社交互动&#xff1a;UTONMOS元宇宙游戏将越来越注重社交互动&am…

Linux系统宝塔面板搭建Typecho博客并实现公网访问本地网站【内网穿透】

文章目录 前言1. 安装环境2. 下载Typecho3. 创建站点4. 访问Typecho5. 安装cpolar6. 远程访问Typecho7. 固定远程访问地址8. 配置typecho 前言 Typecho是由type和echo两个词合成的&#xff0c;来自于开发团队的头脑风暴。Typecho基于PHP5开发&#xff0c;支持多种数据库&#…

Windows服务器:通过nginx反向代理配置HTTPS、安装SSL证书

先看下效果&#xff1a; 原来的是 http&#xff0c;配置好后 https 也能用了&#xff0c;并且显示为安全链接。 首先需要 SSL证书 。 SSL 证书是跟域名绑定的&#xff0c;还有有效期。 windows 下双击可以查看相关信息。 下载的证书是分 Apache、IIS、Tomcat 和 Nginx 的。 我…

9.10目标和(LC494-M)

算法&#xff1a; 加法的绝对值的集合left 减法的绝对值的集合right nums集合的总和sum 这里的left和right都是绝对值&#xff1a; leftrightsum → rightsum-left left-righttarget → left-(sum-left) target → left (target sum)/2 &#xff0c;target …

最新AI系统ChatGPT网站H5系统源码,支持Midjourney绘画

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

python封装,继承,复写详解

目录 1.封装 2.继承 复写和使用父类成员 1.封装 class phone:__voltage 0.5def __keepsinglecore(self):print("单核运行")def callby5g(self):if self.__voltage > 1:print("5g通话开启")else:self.__keepsinglecore()print("不能开启5g通…

xshell安装java/jdk

1.下载jdk wget https://download.java.net/java/GA/jdk11/13/GPL/openjdk-11.0.1_linux-x64_bin.tar.gz 2.解压jdk安装包 tar -zxvf openjdk-11.0.1_linux-x64_bin.tar.gz 其中第三步 编辑 ~/.bashrc 或 ~/.bash_profile 文件 打开vim文本编辑器 vim ~/.bash_profile export …

TT-100K数据集

TT-100K数据集 TT100K数据集是由清华大学和腾讯联合实验室整理并公布的一个大型交通标志数据集。已整理好由xml格式和txt格式。共6105张图片。 有偿分享。可以加我qq&#xff1a;2638351996。注明来意&#xff01;&#xff01;&#xff01;&#xff01;