Tomcat 8.5 源码分析

一、获取源码并启动程序

获取教程地址

总体架构

在这里插入图片描述

二、Tomcat的启动入口

Catalina类主要负责 具体的管理类,而Bootstrap类是启动的入口(main方法)。

/*** Main method and entry point when starting Tomcat via the provided* scripts.** @param args Command line arguments to be processed*/public static void main(String args[]) {synchronized (daemonLock) {if (daemon == null) {// Don't set daemon until init() has completedBootstrap bootstrap = new Bootstrap();try {bootstrap.init();} catch (Throwable t) {handleThrowable(t);t.printStackTrace();return;}daemon = bootstrap;} else {// When running as a service the call to stop will be on a new// thread so make sure the correct class loader is used to// prevent a range of class not found exceptions.Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);}}try {String command = "start";if (args.length > 0) {command = args[args.length - 1];}if (command.equals("startd")) {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.");}} catch (Throwable t) {// Unwrap the Exception for clearer error reportingif (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}

2.1 Bootstrap#init()

	/*** Initialize daemon.* @throws Exception Fatal initialization error*/public void init() throws Exception {//1、初始化类加载器initClassLoaders();//2、设置当前线程的上下文加载器为catalinaLoader      Thread.currentThread().setContextClassLoader(catalinaLoader);//3、预先加载tomcat、javax包的自定义类SecurityClassLoad.securityClassLoad(catalinaLoader);// 加载启动类以及调用setParentClassLoad()方法      if (log.isDebugEnabled())if (log.isDebugEnabled()) {log.debug("Loading startup class");}//4.通过反射创建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;Method method =startupInstance.getClass().getMethod(methodName, paramTypes);method.invoke(startupInstance, paramValues);//5.将startupInstance对象赋值给catalinaDaemoncatalinaDaemon = startupInstance;}

关键步骤

  1. 初始化类加载器
  2. 设置当前线程的上下文加载器为catalinaLoader
  3. 预先加载tomcat、javax包的自定义类
  4. 通过反射创建Catalina对象
  5. 将startupInstance对象赋值给catalinaDaemon

在这里插入图片描述

2.2 Bootstrap#init()#initClassLoaders()

init()初始化的方法也相对的简单,首先调用initClassLoaders()初始化类加载器,使得tomcat可以加载应用程序类,接着设置当前线程的上下文加载器为CatalinaLoader。

 private void initClassLoaders() {try {commonLoader = createClassLoader("common", null);if (commonLoader == null) {// no config file, default to this loader - we might be in a 'single' env.commonLoader = this.getClass().getClassLoader();}//在initClassLoaders()初始化方法中可发现会创建三种类加载器并赋予成员变量,其中catalinaLoader与sharedLoader加载器的父加载器都是commonLoader。catalinaLoader = createClassLoader("server", commonLoader);sharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}

在initClassLoaders()初始化方法中可发现会创建三种类加载器并赋予成员变量,其中catalinaLoader与sharedLoader加载器的父加载器都是commonLoader。

2.3 Catalina#load()

load()方法主要完成StardardServer及子组件的初始化,下图是该方法的主要序列流程:

 public void load() {if (loaded) {return;}loaded = true;long t1 = System.nanoTime();initDirs();// Before digester - it may be neededinitNaming();// Create and execute our DigesterDigester digester = createStartDigester();InputSource inputSource = null;InputStream inputStream = null;File file = null;try {try {file = configFile();//读取serve.xml文件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);}} try {inputSource.setByteStream(inputStream);digester.push(this);//解析serve.xml文件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}}}getServer().setCatalina(this);getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());// Stream redirectioninitStreams();// Start the new servertry {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);}}}

在这里插入图片描述

2.4 standerServer#initInternal()

在执行LifecycleBase#initInternal()抽象方法时,将由standerServer#initInternal()的方法完成初始化。

 @Overrideprotected void initInternal() throws LifecycleException {//1、调用LifecycleMBeanBase#initInternal方法super.initInternal();//2、注册全局字符串缓存,如果有多个Server则也会注册多个字符串缓存对象onameStringCache = register(new StringCache(), "type=StringCache");//3、注册MBeanFactoryMBeanFactory factory = new MBeanFactory();factory.setContainer(this);onameMBeanFactory = register(factory, "type=MBeanFactory");//4、注册全局命名资源并初始化,该全局命名资源是配置在server.xml中globalNamingResources.init();//5、使用catalina的父加载器校验系统JAR包是否包含MANIFEST文件等,此处忽略部分代码//6、初始化自定义的servicefor (Service service : services) {service.init();}}

而service组件的init()方法主要做了以下事情:

  1. 初始化StandardEngine容器。
    • 创建指定或默认的Realm。
  2. 初始化绑定的StandardThreadExecutor线程执行器。
  3. 初始化绑定的MapperListener监听器。
  4. 更新mapper监听器的生命周期状态为LifecycleState.STARTING。
  5. 查找并绑定默认的虚拟主机Host。
  6. 绑定该监听器到Engine及子容器。
  7. 注册Engine容器到绑定的虚拟主机Host以及上下文Context、Wrapper。
  8. 初始化绑定的所有connector连接器。
    /*** Invoke a pre-startup initialization. This is used to allow connectors to bind to restricted ports under Unix* operating environments.*/@Overrideprotected void initInternal() throws LifecycleException {super.initInternal();if (engine != null) {engine.init();}// Initialize any Executorsfor (Executor executor : findExecutors()) {if (executor instanceof JmxEnabled) {((JmxEnabled) executor).setDomain(getDomain());}executor.init();}// Initialize mapper listenermapperListener.init();// Initialize our defined Connectorssynchronized (connectorsLock) {//初始化所有关联的connector连接器for (Connector connector : connectors) {try {connector.init();} catch (Exception e) {String message = sm.getString("standardService.connector.initFailed", connector);log.error(message, e);if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {throw new LifecycleException(message);}}}}}

在整个service初始化过程中,connector连接器的初始化尤其重要,以下为connector连接器初始化代码:

  1. 创建、初始化Coyote适配器,并绑定APR或HTTP1.1协议处理器。
  2. 检查parseBodyMethodsSet是否有值,若没则设置为POST方法。
  3. 判断APR的native库是否必须且协议处理器实例是否已创建,若都不满足则抛出异常。
  4. 判断APR本地库是否必须且APR协议处理器是否可用,若都不满足则抛出异常。
  5. 判断APR协议处理器是否可用且Apr协议处理器是否使用OpenSSL且协议处理器是否AbstractHttp11JsseProtocol类型。
    • 判断是否启用SSL且其类名为空,若满足则使用OpenSSLImplementation类名(OpenSSL与JSSE的配置兼容)。
    • 否则执行步骤6
  6. 初始化协议处理器(该协议处理器实例时在server.xml解析时创建的)。
@Override
protected void initInternal() throws LifecycleException {super.initInternal();// 1、创建并初始化Coyote适配器,并关联至协议处理器中adapter = new CoyoteAdapter(this);protocolHandler.setAdapter(adapter);//2、确认parseBodyMethodsSet有默认值,parsetBodyMethods默认值为POST方法if (null == parseBodyMethodsSet) {setParseBodyMethods(getParseBodyMethods());}//3、判断是否需要APR本地库 AND 判断Apr协议处理器实例是否被创建,若是Apr本地路且没创建Apr协议处理器则抛出异常if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",getProtocolHandlerClassName()));}//4、判断是否需要APR/native库 AND Apr协议处理器是否可用,若是需要Apr本地库且没有Apr协议处理器可用则抛出异常if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",getProtocolHandlerClassName()));}//5、判断Apr协议处理器是否可用 AND Apr协议处理器是否使用OpenSSL AND 协议处理器是否AbstractHttp11JsseProtocol类型if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&protocolHandler instanceof AbstractHttp11JsseProtocol) {AbstractHttp11JsseProtocol<?> jsseProtocolHandler =(AbstractHttp11JsseProtocol<?>) protocolHandler;//5.1、如果SSL启用了且SSL实现类名为空,则使用OpenSSLImplementation类名if (jsseProtocolHandler.isSSLEnabled() &&jsseProtocolHandler.getSslImplementationName() == null) {// 如果APR可用,可以使用OpenSSL,因为OpenSSL与JSSE的配置兼容。jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());}}try {//协议处理器执行初始化(在server.xml解析时创建protocolHandler)protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}
}

2.5Catalina#start()

在这里插入图片描述

public void start(){//判断server是否为空,为空则执行初始化if (getServer() == null) {load();}//若server为空,说明未能正确配置server.xml或者server初始化时异常if (getServer() == null) {log.fatal("Cannot start server. Server instance is not configured.");return;}//此处省略部分代码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;}//此处忽略部分代码// 注册关闭钩子if (useShutdownHook) {if (shutdownHook == null) {shutdownHook = new CatalinaShutdownHook();}Runtime.getRuntime().addShutdownHook(shutdownHook);// If JULI is being used, disable JULI's shutdown hook since// shutdown hooks run in parallel and log messages may be lost// if JULI's hook completes before the CatalinaShutdownHook()LogManager logManager = LogManager.getLogManager();if (logManager instanceof ClassLoaderLogManager) {((ClassLoaderLogManager) logManager).setUseShutdownHook(false);}}if (await) {//创建ServerSocker并监听端口await();//停止standardServer实例stop();}
}

上面主要是启动tomcat中各个组件、容器,这时候还需要创建等待关闭tomcat的ServerSocket以及它监听的端口,而await()方法的作用正是创建ServerSocket并等待接收关闭命令的。

@Override
public void await(){//该处省略部分代码...//创建ServerSocker并监听端口try {awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));} catch (IOException e) {log.error("StandardServer.await: create[" + address+ ":" + port+ "]: ", e);return;}try {awaitThread = Thread.currentThread();// 循环等待有效连接和命令while (!stopAwait) {ServerSocket serverSocket = awaitSocket;if (serverSocket == null) {break;}// 等待下一个连接Socket socket = null;StringBuilder command = new StringBuilder();try {InputStream stream;long acceptStartTime = System.currentTimeMillis();try {socket = serverSocket.accept();socket.setSoTimeout(10 * 1000);  // Ten secondsstream = socket.getInputStream();} catch (SocketTimeoutException ste) {log.warn(sm.getString("standardServer.accept.timeout",Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);continue;} catch (AccessControlException ace) {log.warn(sm.getString("standardServer.accept.security"), ace);continue;} catch (IOException e) {if (stopAwait) {break;}log.error(sm.getString("standardServer.accept.error"), e);break;}// 从套接字读取一组字符int expected = 1024; // Cut off to avoid DoS attackwhile (expected < shutdown.length()) {if (random == null)random = new Random();expected += (random.nextInt() % 1024);}while (expected > 0) {int ch = -1;try {ch = stream.read();} catch (IOException e) {log.warn(sm.getString("standardServer.accept.readError"), e);ch = -1;}// 若字符是控制字符或者EOF(-1)则终止循环if (ch < 32 || ch == 127) {break;}command.append((char) ch);expected--;}} finally {// 完成操作后关闭sockettry {if (socket != null) {socket.close();}} catch (IOException e) {// Ignore}}// 判断命令内容是否SHUTDOWNboolean match = command.toString().equals(shutdown);if (match) {log.info(sm.getString("standardServer.shutdownViaPort"));break;} elselog.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));}} finally {ServerSocket serverSocket = awaitSocket;awaitThread = null;awaitSocket = null;// 关闭ServerScoketif (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {}}}
}

结束

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

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

相关文章

用C语言写一个压缩文件的程序

本篇目录 数据在计算机中的表现形式huffman 编码将文件的二进制每4位划分&#xff0c;统计其值在文件中出现的次数构建二叉树搜索二叉树的叶子节点运行并输出新的编码文件写入部分写入文件首部写入数据部分压缩运行调试解压缩部分解压缩测试为可执行文件配置环境变量总结完整代…

【Nginx07】Nginx学习:HTTP核心模块(四)错误页面与跳转

Nginx学习&#xff1a;HTTP核心模块&#xff08;四&#xff09;错误页面与跳转 最最核心的部分学习完了&#xff0c;但其实还有更多的内容要等待着我们探索。今天我们先来看到的就是关于错误页面的设置以及 301、302 跳转相关的内容。这两块内容都有一个特点&#xff0c;那就是…

LeetCode·每日一题·931. 下降路径最小和·记忆化搜索

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/minimum-falling-path-sum/solutions/2341965/ji-yi-hua-sou-suo-zhu-shi-chao-ji-xiang-3n58v/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&am…

既好用还不贵!云服务器选型六大理由 ,最后两条直击用户内心

“预算不够&#xff0c;腾讯云、百度云、阿里云&#xff0c;到底购买哪个更划算?”这个问题&#xff0c;很多朋友都跟我提过&#xff0c;选择最适合的云服务提供商并不是一件轻松的任务&#xff0c;因为每家公司都有各自的优势和限制。 **拿我接触的一个例子说一说&#xff…

【MQTT】Esp32数据上传采集:最新mqtt插件(支持掉线、真机调试错误等问题)

前言 这是我在Dcloud发布的插件-最完整Mqtt示例代码&#xff08;解决掉线、真机调试错误等问题&#xff09;&#xff0c;经过整改优化和替换Mqtt的js文件使一些市场上出现的问题得以解决&#xff0c;至于跨端出问题&#xff0c;可能原因有很多&#xff0c;例如&#xff0c;合法…

Linux slab 分配器源码解析

文章目录 前言一、slab分配器1.1 简介1.2 高速缓存描述符1.3 架构图 二、相关结构体2.1 struct array_cache2.2 struct kmem_list32.3 struct slab2.3.1 简介2.3.2 OFF_SLAB 三、创建和释放slab3.1 创建slab3.1.1 kmem_getpages3.1.2 alloc_slabmgmt3.1.3 slab_map_pages 3.2 释…

MySQL之DML和DDL

1、显示所有职工的基本信息&#xff1a; 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 3、求出所有职工的人数。 4、列出最高工和最低工资。 5、列出职工的平均工资和总工资。 6、创建一个只有职工号、姓名和参加工作的新表&#xff0c;名为工作日期表。 …

套接字属性

一、选项的级别 1. 基本概念 设置套接字的选项对套接字进行控制 除了设置选项外&#xff0c;还可以获取选项 选项的概念相当于属性&#xff0c;所以套接字选项也可说是套接字属性 有些选项&#xff08;属性&#xff09;只可获取&#xff0c;不可设置&#xff1b; 有些选项既可…

AtcoderABC258场

A - When? A - When? 题目大意 给定一个整数K&#xff0c;表示从日本标准时间21:00开始经过的分钟数。要求将该时间转换为24小时制的时间&#xff08;HH:MM格式&#xff09;。 思路分析 可直接分时间打印。关于格式&#xff0c;填充0&#xff0c;打印时间&#xff0c;题解…

git学习笔记

up:b站迷斯特航 两个版本的项目版本控制&#xff1a; 分支操作&#xff1a; 基本操作&#xff1a; 克隆远程项目到本地&#xff1a; git clone https://github.com/zhoeujei/rknn-coal-ai.git 修改提交到本地仓库&#xff1a;git add rknn_yolov5_demo/CMakeLists.txt&#…

使用C语言连接MySQL

目录 一、引入库 1.1 下载库文件 1.2 在项目中引入库 二、使用库 2.1 连接数据库 2.2 SQL请求 2.3 获取查询结果 2.4 使用案例 一、引入库 1.1 下载库文件 要使用C语言连接MySQL&#xff0c;需使用MySQL官网提供的库 MySQL :: Download Connector/Chttps://dev.mysq…