Tomcat源码解析——Tomcat的启动流程

一、启动脚本

        当我们在服务启动Tomcat时,都是通过执行startup.sh脚本启动。

        

        在Tomcat的启动脚本startup.sh中,最终会去执行catalina.sh脚本,传递的参数是start。

        

        在catalina.sh脚本中,前面是环境判断和初始化参数,最终根据传递的start来执行上图的代码,最终会调用Tomcat的Bootstrap启动类的main方法,传递的参数是start。

二、源码解析

        为了更容易的理解源码,整个系列中Tomcat的运行模式采用BIO的方式。

    public static void main(String args[]) {if (daemon == null) {Bootstrap bootstrap = new Bootstrap();try {//初始化bootstrap.init();} catch (Throwable t) {handleThrowable(t);t.printStackTrace();return;}daemon = bootstrap;} else {Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);}try {String command = "start";if (args.length > 0) {command = args[args.length - 1];}//脚本传递的是startif (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")) {//加载和启动整个Tomcatdaemon.setAwait(true);daemon.load(args);daemon.start();} 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) {if (t instanceof InvocationTargetException &&t.getCause() != null) {t = t.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}

        在启动类的main方法中,创建了一个启动类的实例,然后进行初始化,最后再根据启动命令传递进来的参数,也就是上文中的 start 来执行对应的逻辑,即加载和启动整个Tomcat。

    public void init() throws Exception {//设置容器的路径和脚本中的基本信息setCatalinaHome();setCatalinaBase();//初始化Tomcat的三大类加载器initClassLoaders();//该类加载器用于加载这个源码的类Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);//创建一个容器的启动实例,待会用于加载server.xmlClass<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");Object startupInstance = startupClass.newInstance();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);catalinaDaemon = startupInstance;}

        在初始化启动器是,主要是创建三大类加载器(commonLoader、catalinaLoader、sharedLoader)并设置好父子关系,最后再创建一个启动的实例,主要是用来解析server.xml文件。

        在Tomcat中一共有四大类加载器,如下图:

类加载器作用父加载器

commonLoader(共同类加载器)

加载$CATALINA_HOME/lib下的类加载器应用类加载器

catalinaLoader(容器类加载器)

加载Tomcat应用服务器的类加载器,可以理解为加载Tomcat源码中的类共同类加载器

sharedLoader(共享类加载器)

加载应用类加载器的共享的类加载器,例如相同版本的mysql驱动等共同类加载器
webappLoader(应用类加载)加载web应用下的类类加载,每个web应用之间是相互隔离的共享类加载器

        commonLoader、catalinaLoader、sharedLoader可以在tomcat下的conf/catalina.properties文件中修改。

        初始化完成之后,会根据启动参数 start,然后执行daemon.load(args),即加载。

    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);method.invoke(catalinaDaemon, param);}

        加载就是通过反射去调用Catalina的load方法。

    public void load() {//找到脚本中设置的路径initDirs();initNaming();//创建一个解析器Digester digester = createStartDigester();InputSource inputSource = null;InputStream inputStream = null;File file = null;try {try {//读取server.xml文件file = configFile();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);}}if (inputStream == null) {try {inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail",getConfigFile()), e);}}}if (inputStream == null) {try {inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml");inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString());} catch (Exception e) {if (log.isDebugEnabled()) {log.debug(sm.getString("catalina.configFail","server-embed.xml"), e);}}}if (inputStream == null || inputSource == null) {if (file == null) {log.warn(sm.getString("catalina.configFail",getConfigFile() + "] or [server-embed.xml]"));} else {log.warn(sm.getString("catalina.configFail",file.getAbsolutePath()));if (file.exists() && !file.canRead()) {log.warn("Permissions incorrect, read permission is not allowed on the file.");}}return;}try {//解析server.xml文件,把server.xml中的每一个标签都转换成对应的实例对象inputSource.setByteStream(inputStream);digester.push(this);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);initStreams();try {//初始化最顶层的组件,会导致Service组件和Connector组件也跟着初始化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);}}}

        在Catalina的load方法中,会找到server.xml文件,然后解析标签并创建出对应的实例对象,最终在调用最顶层的Server组件的init方法,会调用Service组件的初始化,而Service组件的会调用Connector组件的初始化(容器的初始化是懒加载的,即有请求达到时才开始初始化)。

        在上面3个组件的初始化中最值得关注的是Connector组件的初始化,因为它会绑定一个端口并且添加对应的协议处理器,从而等待请求。

Connector:protected void initInternal() throws LifecycleException {super.initInternal();//创建一个适配器adapter = new CoyoteAdapter(this);//协议处理器在连接器创建时也跟着创建了protocolHandler.setAdapter(adapter);if( null == parseBodyMethodsSet ) {setParseBodyMethods(getParseBodyMethods());}//...省略try {//协议处理器初始化protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}mapperListener.init();}Http11Protocol:public void init() throws Exception {//...省略try {endpoint.init();} catch (Exception ex) {getLog().error(sm.getString("abstractProtocolHandler.initError",getName()), ex);throw ex;}}JIoEndpoint:public final void init() throws Exception {testServerCipherSuitesOrderSupport();if (bindOnInit) {bind();bindState = BindState.BOUND_ON_INIT;}}public void bind() throws Exception {if (acceptorThreadCount == 0) {acceptorThreadCount = 1;}if (getMaxConnections() == 0) {setMaxConnections(getMaxThreadsExecutor(true));}if (serverSocketFactory == null) {if (isSSLEnabled()) {serverSocketFactory =handler.getSslImplementation().getServerSocketFactory(this);} else {serverSocketFactory = new DefaultServerSocketFactory(this);}}//创建ServerSocket绑定监听端口if (serverSocket == null) {try {if (getAddress() == null) {serverSocket = serverSocketFactory.createSocket(getPort(),getBacklog());} else {serverSocket = serverSocketFactory.createSocket(getPort(),getBacklog(), getAddress());}} catch (BindException orig) {String msg;if (getAddress() == null)msg = orig.getMessage() + " <null>:" + getPort();elsemsg = orig.getMessage() + " " +getAddress().toString() + ":" + getPort();BindException be = new BindException(msg);be.initCause(orig);throw be;}}}

        在连接器的初始化方法中,会调用协议处理器的初始化方法,协议处理器会调用Endpoint的初始化方法,最终在Endpoint中完成了ServerSocket的创建,并且绑定了端口(此时还不能接受处理HTTP请求)

        当Catalina的load方法调用完成后,除了懒加载的容器组件还未创建,其它组件都已经创建出来了,下一步就是启动这些组件(上面只是初始化,组件还没开始工作)。

    public void start()throws Exception {if( catalinaDaemon==null ) init();Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);method.invoke(catalinaDaemon, (Object [])null);}

        跟调用load方法一样,也是通过反射去调用Catalina的start方法。

    public void start() {//...省略try {//调用Server的启动方法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;}//...省略}

        通过调用最顶层Server组件的start方法,会导致Service组件的start被调用,而Service组件的会调用Connector组件的start方法。

        此处值得关注的还是Connector的start方法,在上文中,ServerSocket已经被创建并且绑定了端口,但是还没有去执行接受连接的方法。  

Connector:protected void startInternal() throws LifecycleException {//...省略try {//调用协议处理器的startprotocolHandler.start();} catch (Exception e) {//...省略}mapperListener.start();}Http11Protocolpublic void start() throws Exception {try {endpoint.start();} catch (Exception ex) {}}JIoEndpointpublic final void start() throws Exception {if (bindState == BindState.UNBOUND) {bind();bindState = BindState.BOUND_ON_START;}startInternal();}public void startInternal() throws Exception {if (!running) {running = true;paused = false;//创建一个线程池if (getExecutor() == null) {createExecutor();}initializeConnectionLatch();//创建连接接收器并放入线程池中接受连接startAcceptorThreads();//创建一个超时处理线程Thread timeoutThread = new Thread(new AsyncTimeout(),getName() + "-AsyncTimeout");timeoutThread.setPriority(threadPriority);timeoutThread.setDaemon(true);timeoutThread.start();}}protected final void startAcceptorThreads() {int count = getAcceptorThreadCount();//创建连接接收器acceptors = new Acceptor[count];for (int i = 0; i < count; i++) {acceptors[i] = createAcceptor();String threadName = getName() + "-Acceptor-" + i;acceptors[i].setThreadName(threadName);//一个连接接收器对应一个线程Thread t = new Thread(acceptors[i], threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}}Acceptor:        public void run() {//...省略try {countUpOrAwaitConnection();Socket socket = null;try {//此处调用ServerSocket的阻塞接受连接方法socket = serverSocketFactory.acceptSocket(serverSocket);} catch (IOException ioe) {countDownConnection();// Introduce delay if necessaryerrorDelay = handleExceptionWithDelay(errorDelay);// re-throwthrow ioe;}//...省略}

          连接器start中调用协议处理器的start,协议处理器最终调用Endpoint的start,最终在Endpoint中创建了Acceptor(接收器),并且将Acceptor放入一个线程中异步处理(因为BIO的socket会阻塞),此刻整个tomcat的启动流程大致完成,Tomcat启动完成之后,就是在Acceptor中接受请求并处理了。

        Tomcat启动时序图:

        

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

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

相关文章

MES生产管理系统:私有云、公有云与本地化部署的比较分析

随着信息技术的迅猛发展&#xff0c;云计算作为一种新兴的技术服务模式&#xff0c;已经深入渗透到企业的日常运营中。在众多部署方式中&#xff0c;私有云、公有云和本地化部署是三种最为常见的选择。它们各自具有独特的特点和适用场景&#xff0c;并在不同程度上影响着企业的…

.net框架和c#程序设计第三次测试

目录 一、测试要求 二、实现效果 三、实现代码 一、测试要求 二、实现效果 数据库中的内容&#xff1a; 使用数据库中的账号登录&#xff1a; 若不是数据库中的内容&#xff1a; 三、实现代码 login.aspx文件&#xff1a; <% Page Language"C#" AutoEventW…

8:系统开发基础--8.5:系统设计、8.6:系统测试 、8.7:软件维护 、8.8:软件质量保证、8.9:软件文档

转上一节&#xff1a; http://t.csdnimg.cn/X0GjWhttp://t.csdnimg.cn/X0GjW 8.5&#xff1a;系统设计 考点1&#xff1a;系统设计概述 1&#xff1a;软件设计的任务与活动 体系结构设计&#xff1a;定义软件系统各主要部件之间的关系。 数据设计&#xff1a;基于E-R图确定…

OpenHarmony实战开发-异步并发概述 (Promise和async/await)。

Promise和async/await提供异步并发能力&#xff0c;是标准的JS异步语法。异步代码会被挂起并在之后继续执行&#xff0c;同一时间只有一段代码执行&#xff0c;适用于单次I/O任务的场景开发&#xff0c;例如一次网络请求、一次文件读写等操作。 异步语法是一种编程语言的特性&…

探索设计模式的魅力:深度挖掘响应式模式的潜力,从而精准优化AI与机器学习项目的运行效能,引领技术革新潮流

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 挖掘响应式模式&#xff0c;优化AI与机器学习项目性能&#xff0c;引领技术新潮流 ✨机器学习界的…

快速上手Vue

目录 概念 创建实例 插值表达式 Vue响应式特性 概念 Vue是一个用于 构建用户界面 的 渐进式 框架 构建用户界面&#xff1a;基于数据渲染出用户看到的页面 渐进式&#xff1a;Vue相关生态&#xff1a;声明式渲染<组件系统<客户端路由<大规模状态管理<构建工具 V…

第十二讲 查询计划 优化

到目前为止&#xff0c;我们一直在说&#xff0c;我们得到一个 SQL 查询&#xff0c;我们希望可以解析它&#xff0c;将其转化为某种逻辑计划&#xff0c;然后生成我们可以用于执行的物理计划。而这正是查询优化器【Optimizer】的功能&#xff0c;对于给定的 SQL &#xff0c;优…

Ubuntu Desktop 免费的文件 / 目录差异比较工具 (Beyond Compare 为收费软件)

Ubuntu Desktop 免费的文件 / 目录差异比较工具 [Beyond Compare 为收费软件] 1. Installation2. Meld Diff Viewer3. Lock to LauncherReferences Meld - Visual diff and merge tool https://meldmerge.org/ Meld helps you compare files, directories, and version contro…

【MATLAB源码-第50期】基于simulink的BPSK调制解调仿真,输出误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. Bernoulli Binary: 这个模块生成伯努利二进制随机数&#xff0c;即0或1。这些数字表示要传输的原始数字信息。 2. Unipolar to Bipolar Converter: 此模块将伯努利二进制数据从0和1转换为-1和1&#xff0c;这是BPSK调制的…

C语言之offsetof实现分析(九十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

基于Springboot+Vue的Java项目-高校心理教育辅导系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂

001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂 文章目录 001_IoT/物联网通信协议基础: HTTP、Websocket、MQTT、AMQP、COAP、LWM2M一文搞懂创作背景通信模型ISO/OSI七层模型 和 TCP/IP四层模型网络通信数据包格式&#xff08;Ethernet II&…

在MOS管栅极前加100Ω电阻,有啥妙用

我们经常会听到在MOSFET栅极前增加一个电阻。那么&#xff0c;为什么要增加这个电阻&#xff0c;进一步地来讲&#xff0c;为什么要增加一个100Ω电阻&#xff1f; 在MOSFET的栅极前增加一个电阻&#xff1f; MOS管是电压型控制器件&#xff0c;一般情况下MOS管的导通&#x…

基于ollama搭建本地chatGPT

ollama帮助我们可以快速在本地运行一个大模型&#xff0c;再整合一个可视化页面就能构建一个chatGPT&#xff0c;可视化页面我选择了chat-ollama&#xff08;因为它还能支持知识库&#xff0c;可玩性更高&#xff09;&#xff0c;如果只是为了聊天更推荐chatbox 部署步骤 下载…

unity记一下如何播放动画

我使用的版本是2022.3.14fc 展开你的模型树&#xff0c;是会出现这个三角形的东西的 然后在资源面板创建一个animation controller 进去之后&#xff0c;把三角形拖进去&#xff0c;就会出现一个动画&#xff0c;然后点击他 在左侧给他创建这么个状态名字&#xff0c;类型…

(一)基于IDEA的JAVA基础15

还是先来说一下: Arrays工具类 Arrays是java.util包提供的工具类 提供了操作数组的方法&#xff0c;如排序,查询等。 如排序(升序)使用sort方法 语法: Arrays.sort(数组名)&#xff1b; 还是直接写来看看: public class Test01 { public static void main(String[] args)…

Swagger API 文档 | SpringBoot 3.x 集成 SpringDoc

文章目录 常规方式第 1 步:添加依赖第 2 步:配置 API 信息及全局参数配置 OpenAPI 文档配置单个 OpenAPI 文档 - 方式 1配置单个 OpenAPI 文档 - 方式 2配置多个 OpenAPI 文档其它 SpringDoc 及 Swagger-UI 配置第 3 步:添加 Swagger3 注解Swagger2 和 Swagger3 注解对应关系…

云服务器web环境之mariadb

1.安装mariadb服务 yum install mariadb-server 启动mariadb服务 systemctl start mariadb.service 输入mysql就能使用数据库了。 2.服务相关操作 systemctl stop mariadb.service systemctl restart mariadb.service 2.配置开机自启动 systemctl enable mariadb.serv…

vue快速入门(二十四)输入停顿再进行响应

注释很详细&#xff0c;直接上代码 上一篇 新增内容 使用侦听器监视数据变化情况使用clearTimeout与定时器实现停顿一段时间再操作内容 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"view…

64B/66B GT Transceiver 配置

一、前言 前一篇文章已经讲述了64B/66B的编码原理&#xff0c;此篇文章来配置一下7系列GT的64B/66B编码。并讲述所对应的例子工程的架构&#xff0c;以及部分代码的含义。 二、IP核配置 1、打开7 Series FPGAs Transceiver Wizards&#xff0c;选择将共享逻辑放置在example …