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

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 {// 默认参数为startString 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() methodif (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 loaderif (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方法,参数为nullmethod.invoke(catalinaDaemon, param);
}

start方法:

 public void start()throws Exception {if( catalinaDaemon==null ) init();// 反射调用catalina的start方法,参数为nullMethod 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,下面会置为trueif (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=trueif (await) {// main线程等待,等待接收shutdown命令,接受到则跳出阻塞await();// 跳出阻塞,执行Server.stop();stop();}
}

2.1、注册shutdown钩子

  • 注册shutdown钩子(CatalinaShutdownHook),即注册一个线程任务main结束时调用
  • getServer() != null 如果server未停止调用Catalina的stop方法停止
protected class CatalinaShutdownHook extends Thread {@Overridepublic 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直接再终止Serverif( 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 connectionSocket 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;} elselog.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.hqwc.cn/news/499345.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xss过waf的小姿势

今天看大佬的视频学到了几个操作 首先是拆分发可以用self将被过滤的函数进行拆分 如下图我用self将alert拆分成两段依然成功执行 然后学习另一种姿势 <svg id"YWxlcnQoIlhTUyIp"><img src1 οnerrοr"window[eval](atob(document.getElementsByTagNa…

深入理解Java泛型及其在实际编程中的应用

第1章&#xff1a;泛型的起源与重要性 大家好&#xff0c;我是小黑&#xff0c;在Java里&#xff0c;泛型&#xff08;Generics&#xff09;是一种不可或缺的特性&#xff0c;它允许咱们在编码时使用类型&#xff08;Type&#xff09;作为参数。这听起来可能有点绕&#xff0c…

单细胞Seurat - 细胞聚类(3)

本系列持续更新Seurat单细胞分析教程&#xff0c;欢迎关注&#xff01; 维度确定 为了克服 scRNA-seq 数据的任何单个特征中广泛的技术噪音&#xff0c;Seurat 根据 PCA 分数对细胞进行聚类&#xff0c;每个 PC 本质上代表一个“元特征”&#xff0c;它结合了相关特征集的信息。…

【网站项目】424学报稿件管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

ModStartCMS v8.1.0 图片前端压缩,抖音授权登录

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

[LeetCode]143.重排链表

143. 重排链表 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/reorder-list/description/ 题目 示例 解题思路 寻找链表中点 链表逆序 合并链表 注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果。 这样我们的任务即可划分为三步&a…

React入门之React_使用es5和es6语法渲染和添加class

React入门 //react的核心库 <script src"https://cdn.jsdelivr.net/npm/react17/umd/react.development.js"></script> //react操作dom的核心库&#xff0c;类似于jquery <script src"https://cdn.jsdelivr.net/npm/react-dom17/umd/react-dom.…

微信干货知识分享:自动回复

信息太多回复太慢 回复新好友不及时 号太多&#xff0c;携带多个手机太重 今天给大家分享一下微信的隐藏功能 自动通过好友 有新的好友请求时&#xff0c;系统会自动通过好友&#xff0c;以免处理其他工作时错过客户。 通过好友自动回复 当微信号在系统登录&#xff0c;被动…

adb下载安装及使用教程

adb下载安装及使用教程 一、ADB的介绍1.ADB是什么&#xff1f;2.内容简介3.ADB常用命令1. ADB查看设备2. ADB安装软件3. ADB卸载软件4. ADB登录设备shell5. ADB从电脑上发送文件到设备6. ADB从设备上下载文件到电脑7. ADB显示帮助信息 4.为什么要用ADB 二、ADB的下载1.Windows版…

深度学习 精选笔记(7)前向传播、反向传播和计算图

学习参考&#xff1a; 动手学深度学习2.0Deep-Learning-with-TensorFlow-bookpytorchlightning ①如有冒犯、请联系侵删。 ②已写完的笔记文章会不定时一直修订修改(删、改、增)&#xff0c;以达到集多方教程的精华于一文的目的。 ③非常推荐上面&#xff08;学习参考&#x…

欢迎参与2024年 OpenTiny 开源项目用户调研

&#x1f389; 欢迎参与 OpenTiny 开源项目的用户调研 &#x1f389; &#x1f4e3; 调研背景 随着 OpenTiny 开源项目的不断发展&#xff0c;我们一直致力于为开发者提供高质量的 Web 前端开发解决方案。为了更好地满足用户需求&#xff0c;提升项目的实用性和易用性&#x…

【Maven】Maven 基础教程(二):Maven 的使用

《Maven 基础教程》系列&#xff0c;包含以下 2 篇文章&#xff1a; Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置Maven 基础教程&#xff08;二&#xff09;&#xff1a;Maven 的使用 &#x1f60a; 如果您觉得这篇文章有用 ✔️ 的话&#…