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

news2024/11/17 9:58:29

一、启动脚本

        当我们在服务启动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];
            }
            //脚本传递的是start
            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")) {
                //加载和启动整个Tomcat
                daemon.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.xml
        Class<?> 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();
                else
                    msg = 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 {
            //调用协议处理器的start
            protocolHandler.start();
        } catch (Exception e) {
            //...省略
        }
        mapperListener.start();
    }


Http11Protocol

    public void start() throws Exception {
        try {
            endpoint.start();
        } catch (Exception ex) {
            
        }
    }


JIoEndpoint

    public 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 necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                 //...省略
        }

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

        Tomcat启动时序图:

        

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

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

相关文章

架构师系列-搜索引擎ElasticSearch(六)- 映射

映射配置 在创建索引时&#xff0c;可以预先定义字段的类型&#xff08;映射类型&#xff09;及相关属性。 数据库建表的时候&#xff0c;我们DDL依据一般都会指定每个字段的存储类型&#xff0c;例如&#xff1a;varchar、int、datetime等&#xff0c;目的很明确&#xff0c;就…

45.HarmonyOS鸿蒙系统 App(ArkUI)创建列表(List)

列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、音乐列…

Qt快速入门(MV架构之TableView + QStandardItemModel + 自定义代理小案例)

Qt快速入门&#xff08;MV架构之TableView QStandardItemModel 自定义代理小案例&#xff09; 关于MV架构的简单介绍 在Qt框架中&#xff0c;代理&#xff08;Delegate&#xff09;、模型&#xff08;Model&#xff09;和视图&#xff08;View&#xff09;之间的关系构成了…

14_SpringMVC

文章目录 MVCSpringMVC与JavaEE对比SpringMVCSpringMVC的核心流程SpringMVC入门案例RequestMapping注解的使用Handler方法的返回值Handler方法的形参keyvalue形式的请求参数Json请求参数 RESTful风格接口静态资源处理FilterHandlerInterceptor异常处理SpringMVC核心流程流程图 …

自动化收集Unity版本更新日志

自动化收集Unity版本更新日志 &#x1f365;功能介绍&#x1f96a;食用手册填写配置开始搜集 &#x1f368;数据展示 &#x1f365;功能介绍 &#x1f4a1;获取指定年份中所有的Unity版本更新日志。 &#x1f4a1;根据指定字符串过滤。 &#x1f4a1;.收集后自动保存成markdow…

架构师系列-搜索引擎ElasticSearch(四)- 高级查询

ES查询 matchAll 脚本方式 该方式可以通过kabana、curl、elasticsearch-head&#xff08;纯前端&#xff09;去操作 # 默认情况下&#xff0c;es一次展示10条数据,通过from和size来控制分页 # 查询结果详解 GET goods/_search {"query": {"match_all":…

如何在MacOS上使用OpenHarmony SDK交叉编译?

本文以cJSON三方库为例介绍如何通过OpenHarmony的SDK在Mac平台进行交叉编译。 环境准备 SDK准备 我们可以通过 openHarmony SDK 官方发布渠道下载对应mac版本的SDK&#xff0c;当前OpenHarmony MAC版本的SDK有2种&#xff0c;一种是x86架构&#xff0c;另一种是arm64&#x…

二叉树例题分享

文章目录 二叉树例题分享[235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/)[701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/)[108. 将有序数组转换为二叉搜索树…

分享一些有趣的 Linux 命令

1、sl 会显示一辆火车穿过你的终端屏幕 2、cmatrix 在终端中显示类似于《黑客帝国》电影中的绿色数字雨效果 3、fortune 显示一个随机的名人名言或者笑话 4、cowsay 让一头牛说出你输入的话 5、toilet 在终端中将输入的文本以艺术字体的形式呈现 6、figlet 类似于 toile…

Python数据分析案例41——基于CNN-BiLSTM的沪深300收盘价预测

案例背景 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领域过来的小白来问这些神经网络怎么写&#xff0c;怎么搭建&#xff0c;给我一篇论文看看感觉很厉害的样子。我一看&#xff1a;普刊、单变量时间序列预测、一个…

软考中级工程师网络技术第二节网络体系结构

OSPF将路由器连接的物理网络划分为以下4种类型&#xff0c;以太网属于&#xff08;25&#xff09;&#xff0c;X.25分组交换网属于&#xff08;非广播多址网络NBMA&#xff09;。 A 点对点网络 B 广播多址网络 C 点到多点网络 D 非广播多址网络 试题答案 正确答案&#xff1a; …

VS2019调试

最近开始了解单步调试 开始我按下F11键是没用的。 Visual Studio 调试快捷键失效_visual studio code用不了快捷键-CSDN博客 我的F11对应的系统功能是调小音量。 所以有两种模式&#xff1a; (1)按下F11,调小音量 (2)按下F11,单步调试 通过Fnesc键进行模式的切换。 还有一…

数据结构初阶:二叉树(二)

二叉树链式结构的实现 前置说明 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在对二叉树结构掌握还不够深入&#xff0c;为了降低学习成本&#xff0c;此处手动快速创建一棵简单的二叉树&#xff0c;快速进入二…

网络篇10 | 网络层 IP

网络篇10 | 网络层 IP 01 简介02 名称解释03 IP报文格式(IPv4)1&#xff09;4位版本协议(version)2&#xff09;4位首部长度(header length)3&#xff09;8位服务类型(Type Of Service, TOS)4&#xff09;16位总长度5&#xff09;16位(分片)标识6&#xff09;3位(分片)标志7&am…

【算法】回溯:与递归,dfs的同质与分别,剪枝与恢复现场的详细理解,n皇后的回溯解法及算法复杂度分析。

目录 ​编辑 1.什么是回溯 2.关于剪枝 3.关于恢复现场 4.题目&#xff1a;二叉树的所有路径&#xff08;凸显恢复现场&#xff1a;切实感受回溯与深搜&#xff09; 问题分析 ①函数设置为&#xff1a;void Dfs(root) ②函数设置为&#xff1a;void Dfs(root,path) 解题思想&…

unity记一下如何播放动画

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

【JavaEE多线程】理解和管理线程生命周期

目录 ThreadThread类的常用构造方法Thread类的常见属性启动一个线程-start()终止一个线程等待一个线程-join()线程的状态 Thread Thread 就是在 Java 中&#xff0c;线程的代言人。系统中的一个线程&#xff0c;就对应到 Java 中的一个 Thread 对象。围绕线程的各种操作&#…

webrtc中的Track,MediaChannel,MediaStream

文章目录 Track,MediaChannel,MediaStream的关系MediaStream的创建流程创建VideoChannel的堆栈创建VideoStream的堆栈 sdp中媒体参数信息的映射sdp中媒体信息参数设置体系参数设置流程参数映射体系 Track,MediaChannel,MediaStream的关系 Audio/Video track&#xff0c;MediaC…

一款酷黑风个人html引导页

一款酷黑风个人html引导页&#xff0c;如果想要修改的话&#xff0c;请在index.html文件修改图片位置在&#xff0c;images文件夹背景音乐在music文件夹手机端在m文件夹 源码下载 一款酷黑风个人html引导页

Windows Server 2016虚拟机安装教程

一、VMware Workstation虚拟机软件的下载 官网下载入口&#xff1a;​​​​​​Download VMware Workstation Pro - VMware Customer Connect​​​​​ 下载好之后自己看着提示安装软件就好. 二、镜像文件的下载 下载网站入口&#xff1a;MSDN, 我告诉你 - 做一个安静…