Tomcat源码:Container接口

news2025/4/14 8:29:16

参考资料:

《Tomcat - Request请求处理: Container设计》

《Tomcat - Container容器之Engine:StandardEngine》

前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

        写在开头:本文为个人学习笔记,内容比较随意,夹杂个人理解,如有错误,欢迎指正。

前言

        前文中我们介绍了StandServer与StandService的init与start方法,本文我们接着介绍以Engine为首的子容器。由下图可以看出Engine、Host、Context、Wrapper等容器组件都继承了Container接口,从而间接继承了Lifecycle接口,而其抽象实现类ContainerBase又继承了LifecycleMBeanBase类,本文我们就介绍下Container这个接口。

目录

前言

一、Container接口

        1、父子容器交互

        2、事件监听

二、抽象类ContainerBase

        1、子容器相关方法实现

        1.1、添加子容器

        1.2、查找子容器 

        1.3、删除子容器

        2、Lifecycle的模板方法

        2.1、initInternal

        2.2、getStartStopThreadsInternal

        2.3、startInternal

       2.4、threadStart

        2.5、ContainerBackgroundProcessor#run

        2.6、processChildren

        2.7、backgroundProcess


一、Container接口

        1、父子容器交互

        Container的实现类Engine、Host、Context、Wrapper都具有父子关系(注意不是继承),因此Container自然要提供父节点的设置、查找以及子节点的添加、删除、查找。这里注意由于一个父节点可以有多个子节点因此返回的是数组。

public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public Container[] findChildren();
public void removeChild(Container child);

        2、事件监听

        这里不但有Container级别的监听器,还有属性相关的Listener以及触发方法。

public void addContainerListener(ContainerListener listener);
public ContainerListener[] findContainerListeners();
public void removeContainerListener(ContainerListener listener);

public void removePropertyChangeListener(PropertyChangeListener listener);
public void addPropertyChangeListener(PropertyChangeListener listener);

public void fireContainerEvent(String type, Object data);

二、抽象类ContainerBase

        1、子容器相关方法实现

        1.1、添加子容器

    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> dp = new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            addChildInternal(child);
        }
    }

    private void addChildInternal(Container child) {
        synchronized (children) {
            if (children.get(child.getName()) != null) {
                throw new IllegalArgumentException(sm.getString("containerBase.child.notUnique", child.getName()));
            }
            // 给子容器设置父容器并保存起来方便查找
            child.setParent(this);
            children.put(child.getName(), child);
        }
        // 没有将start方法放到synchronized的原因
        // 启动可能是一个缓慢的过程并且锁定子对象可能会导致其他地方出现问题
        try {
            // 判断状态决定是否要启动子容器
            if ((getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) && startChildren) {
                child.start();
            }
        } catch (LifecycleException e) {
            log.error("ContainerBase.addChild: start: ", e);
            throw new IllegalStateException(sm.getString("containerBase.child.start"), e);
        } finally {
            // 触发事件
            fireContainerEvent(ADD_CHILD_EVENT, child);
        }
    }

        1.2、查找子容器 

    protected final HashMap<String, Container> children = new HashMap<>();

    public Container findChild(String name) {
        if (name == null) {
            return null;
        }
        synchronized (children) {
            return children.get(name);
        }
    }

    public Container[] findChildren() {
        synchronized (children) {
            return children.values().toArray(new Container[0]);
        }
    }

        1.3、删除子容器

        因为容器都有生命周期,所以应该是先停止,然后销毁(distroy), 再触发删除事件,最后将children中子容器删除。

    public void removeChild(Container child) {
        if (child == null) {
            return;
        }
        try {
            if (child.getState().isAvailable()) {
                child.stop();
            }
        } catch (LifecycleException e) {
            log.error(sm.getString("containerBase.child.stop"), e);
        }
        try {
            if (!LifecycleState.DESTROYING.equals(child.getState())) {
                child.destroy();
            }
        } catch (LifecycleException e) {
            log.error(sm.getString("containerBase.child.destroy"), e);
        }
        synchronized (children) {
            if (children.get(child.getName()) == null) {
                return;
            }
            children.remove(child.getName());
        }

        fireContainerEvent(REMOVE_CHILD_EVENT, child);
    }

        2、Lifecycle的模板方法

        2.1、initInternal

        initInternal主要创建了一个线程池,参数中的线程池大小由getStartStopThreadsInternal方法决定,这个线程池将会在下文用于启动子容器。

    protected ThreadPoolExecutor startStopExecutor;
    
    protected void initInternal() throws LifecycleException {
        // 创建线程安全的队列
        BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
        // 创建线程池
        startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10,
                TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);
        // 调用父类LifecycleMBeanBase的初始化方法
        super.initInternal();
    }

        2.2、getStartStopThreadsInternal

        该方法获取成员变量startStopThreads,如果值大于0则直接返回,如果小于等于0则进一步再处理,但可以确保返回值必然大于等于1。

    private int getStartStopThreadsInternal() {
        int result = getStartStopThreads();
        if (result > 0) {
            return result;
        }
        // availableProcessors返回可用处理器的Java虚拟机的数量
        result = Runtime.getRuntime().availableProcessors() + result;
        if (result < 1) {
            result = 1;
        }
        return result;
    }

    private int startStopThreads = 1;

    public int getStartStopThreads() {
        return startStopThreads;
    }

        2.3、startInternal

        startInternal负责启动子容器,这里findChildren()方法获取子容器并将子容器的启动封装在一个 StartChild 对象里,然后将这个 StartChild 对象加入到startStopExecutor线程池中,这个 startStopExecutor就是在文中上面介绍到的。
        下方等待执行结果(Future的get方法可以获取执行结果,该方法会阻塞直到任务返回结果),如果执行中出现异常,则将收集到 MultiThrowable 对象了,并抛出 LifecycleException 异常。

    protected synchronized void startInternal() throws LifecycleException {
        logger = null;
        getLogger();
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            ((Lifecycle) cluster).start();
        }
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }
        // 将要启动的子容器加入到线程池中异步启动
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            results.add(startStopExecutor.submit(new StartChild(child)));
        }
        MultiThrowable multiThrowable = null;
        // 获取子容器启动结果
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }

        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
        // 设置状态
        setState(LifecycleState.STARTING);
        threadStart();
    }

         可以看出StartChild类就只是简单执行Container 对象的 start 方法,再结合上文就可以明白子容器是如何启动的了。

    private static class StartChild implements Callable<Void> {
        private Container child;
        StartChild(Container child) {this.child = child;}
        @Override
        public Void call() throws LifecycleException {
            child.start();
            return null;
        }
    }

        最后执行了pipeline的start方法,Pipeline是Container用来处理请求的,Container 处理请求实际上是交给Pipeline处理的,Pipeline串联了一个或多个 Valve组成了一个责任链,用 Value 去处理请求,这部分内容我们会在后续进行介绍。

       2.4、threadStart

        startInternal中最后一行调用了threadStart方法,backgroundProcessorDelay默认值-1可以看出来默认是不执行的,如果子容器中设置了大于0的值(如StandardEngine中设置了10),则会创建一个线程,异步执行ContainerBackgroundProcessor中的run方法。

   private Thread thread = null;
   protected int backgroundProcessorDelay = -1;
 
   protected void threadStart() {
        if (thread != null) {
            return;
        }
        if (backgroundProcessorDelay <= 0) {
            return;
        }
        threadDone = false;
        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(true);
        thread.start();
    }

        2.5、ContainerBackgroundProcessor#run

        ContainerBackgroundProcessor的run方法其实就是延迟调用Container的 backgroundProcess() 方法,然后递归处理调用 ContainerBackgroundProcessor#processChildren 来调用子容器的 backgroundProcess()。如果 Container 是 Context 的实现来的话,还会调用 Context#bind 方法。

    protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
            Throwable t = null;
            String unexpectedDeathMessage = sm.getString("containerBase.backgroundProcess.unexpectedThreadDeath",
                    Thread.currentThread().getName());
            try {
                while (!threadDone) {
                    try {
                        // 延迟backgroundProcessorDelay秒
                        Thread.sleep(backgroundProcessorDelay * 1000L);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                    if (!threadDone) {
                        // 调用processChildren方法
                        processChildren(ContainerBase.this);
                    }
                }
            } catch (RuntimeException | Error e) {
                t = e;
                throw e;
            } finally {
                if (!threadDone) {
                    log.error(unexpectedDeathMessage, t);
                }
            }
        }

        protected void processChildren(Container container) {
            // ...
        }
    }

        2.6、processChildren

        processChildren方法调用当前容器的backgroundProcess方法,随后获取子容器并判断其是否需要延迟调用processChildren方法,最后达到的效果其实是递归调用子容器的backgroundProcess方法。

    protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
            // ...
            processChildren(ContainerBase.this);
        }

        protected void processChildren(Container container) {
            ClassLoader originalClassLoader = null;
            try {
                if (container instanceof Context) {
                    Loader loader = ((Context) container).getLoader();
                    if (loader == null) {
                        return;
                    }
                    originalClassLoader = ((Context) container).bind(false, null);
                }
                // 调用当前容器的backgroundProcess方法
                container.backgroundProcess();
                Container[] children = container.findChildren();
                // 获取子容器并判断其是否需要延迟调用processChildren方法
                // 最后达到的效果其实是递归调用子容器的backgroundProcess方法
                for (Container child : children) {
                    if (child.getBackgroundProcessorDelay() <= 0) {
                        processChildren(child);
                    }
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("containerBase.backgroundProcess.error"), t);
            } finally {
                if (container instanceof Context) {
                    ((Context) container).unbind(false, originalClassLoader);
                }
            }
        }
    }

        backgroundProcess的具体操作由容器实现,这里暂且略过,我们会在介绍StandEngine时介绍。        

        StandardEngine 没有重写ContainerBase 的 backgroundProcess() 方法,而 StandardHost、StandardContext、StandardWrapper 都没有重新给 backgroundProcessorDelay 赋值,所以这些类的 getBackgroundProcessorDelay() 返回的值是 -1,因此都这些类的 backgroundProcess() 都将会执行。

        结合上文,这里需要注意的是threadStart是在startInternal方法中执行的,虽然每个子容器都会调用startInternal方法,但是由于只有StandardEngine重新赋值了backgroundProcessorDelay,因此这一段只有在StandardEngine启动时调用,后续不会重复调用。

        2.7、backgroundProcess

        可以看出ContainerBase的backgroundProcess() 方法依次调用Cluster、Realm、Valve 的 backgroundProcess() 方法,然后触发一个 Lifecycle.PERIODIC_EVENT 事件。

        这里有稍微介绍下Valve,在上文中的startInternal流程中启动了pipeline.start,pipeline是通过将多个value组成了责任链来完成对请求的处理,这里的pipeline.getFirst()便是获取责任链中的第一个Valve节点,调用其backgroundProcess方法,并通过current = current.getNext()不断获取责任链中的下一个Valve节点,以此实现所有Valve的调用。

    public void backgroundProcess() {

        if (!getState().isAvailable()) {
            return;
        }

        Cluster cluster = getClusterInternal();
        if (cluster != null) {
            try {
                cluster.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
            }
        }
        Realm realm = getRealmInternal();
        if (realm != null) {
            try {
                realm.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
            }
        }
        Valve current = pipeline.getFirst();
        while (current != null) {
            try {
                current.backgroundProcess();
            } catch (Exception e) {
                log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
            }
            current = current.getNext();
        }
        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
    }

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

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

相关文章

matplotlib绘图看这篇就够了

导入matplotlib第三方库此外&#xff0c;在matplotlib中我们可以只输入y轴&#xff0c;即为只输入一个数组我们也可以输出&#xff0c;x不为必要条件。而且也可以使用plt.xticks()函数进行设置x轴的label。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Si…

注意力机制中Q和K相乘的意义是什么?为什么Q和K相乘就可以得到它们之间的相似性/权重矩阵呢?

为什么query和key相乘就能得到学生和教师的相似度呢&#xff1f;它的内部原理是什么? 在注意力机制中&#xff0c;query 和 key 相乘得到的相似度其实是通过计算两个向量之间的点积来实现的。具体而言&#xff0c;我们将 query 和 key 进行点积运算后【这里的点积运算可以看作…

从C出发 23 --- 函数专题练习

A&#xff1a;我们可以将 main 理解为操作系统调用的函数&#xff0c;操作系统运行一个应用程序时&#xff0c;就去调用这个应用程序里面的main函数 B: 函数中只能定义变量&#xff0c;定义的变量叫局部变量 C: 从操作系统的角度来看 C 并不一定正确&#xff0c;因为从技术角…

Cadence OrCAD Capture 层次化电路设计展开的方法

&#x1f3e1;《总目录》   &#x1f3e1;《宝典目录》   &#x1f3e1;《上级目录》 目录1&#xff0c;概述2&#xff0c;展开方法3&#xff0c;总结B站关注“硬小二”浏览更多演示视频 1&#xff0c;概述 典型的层次化设计是指顶层模块中&#xff0c;调用1个电路模块超过…

Java中的并发容器

Java 中的 并发容器 1.List 类 list类 线程安全的主要有 Vector 与 CopyOnWriteArrayList a). Vector Vector 相当于在 原有 ArrayList类的基础上将所有方法 变成同步方法 同样的操作还有 Collections.synchronizedList&#xff08;&#xff09; 方法&#xff0c;将原有Lis…

自训练Self-Training学习总结

一、自训练&#xff08;Self-training&#xff09; Self-training是最简单的半监督方法之一&#xff0c;其主要思想是找到一种方法&#xff0c;用未标记的数据集来扩充已标记的数据集。算法流程如下&#xff1a; 首先&#xff0c;利用已标记的数据来训练一个好的模型&#xf…

ch04-损失优化

ch04-损失优化0.引言1.权值初始化1.1. 梯度消失与爆炸1.2. Xavier 初始化1.3. Kaiming 初始化1.4. 常用的权值始化方法1.5. nn.init.calculate_gain1.6. 总结2.损失函数 (一)2.1. 损失函数的概念2.2. 交叉熵损失函数 nn.CrossEntropyLoss2.3. NLL/BCE/BCEWithLogits Loss2.4. 总…

什么原因导致了儿童自闭症?跟父母养育有关吗?

导致儿童自闭症的原因是什么&#xff1f;这和父母的抚养有关吗&#xff1f;学习教育孩子的方法&#xff0c;让孩子快乐健康地成长&#xff0c;是家庭和孩子生活中的一件重要事情。不良的环境和错误的教育会导致儿童自闭症&#xff0c;这是真的吗&#xff1f;自闭症&#xff0c;…

1、vscode搭建C++开发环境及一些配置文件的含义

文章目录一、vscode搭建开发环境1、下载和配置MinGW-w64 编译器套件2、安装到电脑中3、配置环境变量4、测试是否安装成功5、vscode上安装C/C插件二 、配置编译环境时各个文件的含义1、task.json&#xff1a;此文件告诉VS代码如何构建&#xff08;编译&#xff09;程序&#xff…

如何利用 IP 归属地查询 API 精准锁定用户位置

引言 在互联网时代&#xff0c;IP 地址扮演着非常重要的角色&#xff0c;它可以帮助我们追踪网站访问者、优化网络服务等等。而 IP 归属地则更进一步&#xff0c;它可以帮助我们精确地定位 IP 地址所在的地理位置&#xff0c;为数据分析、网络安全、市场调研等领域提供了极大的…

「业务架构」需求工程——需求验证(第4部分)

确保规定要求满足客户需求的过程。需求验证它是一个确保特定需求满足客户需求的过程。它关心的是找到需求中的问题。当这些问题在后期发现时&#xff0c;或者在系统投入使用后&#xff0c;这些问题会导致大量的返工成本。通过系统变更来修复需求问题的成本通常比修复设计或代码…

如何选择 O2OA (翱途) 开发平台的部署架构?

O2OA (翱途) 开发平台 [下称 O2OA 开发平台或者 O2OA] 支持公有云&#xff0c;私有云和混合云部署&#xff0c;也支持复杂的网络结构下的分布式部署。本篇主要介绍 O2OA (翱途) 开发平台支持的部署环境以及常用的集群部署架构。 软硬件环境说明 支持的云化平台&#xff1a; …

微信小程序-组件化

微信小程序-组件化 自定义组件 业务描述&#xff1a;代码中有多处需要引用同一段代码&#xff0c;需要把他封装成一个组件 流程 在根目录创建components用于存放通用组件在创建组件文件夹选择新建components 会自动生成4个文件json文件 会出现"component": true,…

Talk预告 | 浙江大学乔硕斐:语言模型提示推理综述

本期为TechBeat人工智能社区第480期线上Talk&#xff01; 北京时间3月9日(周四)20:00&#xff0c;浙江大学计算机科学与技术硕士——乔硕斐的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “语言模型提示推理综述 ”&#xff0c;届时将分享对语言…

知识点学习登记备份信息

知识点记录 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ovilnIi-1681441105895)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211228090433836.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上…

计算机网络 实验三

⭐计网实验专栏&#xff0c;欢迎订阅与关注&#xff01; ★观前提示&#xff1a;本篇内容为计算机网络实验。内容可能会不符合每个人实验的要求&#xff0c;因此以下内容建议仅做思路参考。 一、实验目的 理解路由器转发分组的机制。 理解路由表的作用、基本结构。 掌握静态路…

Redis常见命令

Redis是典型的key-value数据库&#xff0c;key一般是字符串&#xff0c;而value包含很多不同的数据类型&#xff1a;1. Redis通用命令 通用指令是部分数据类型的&#xff0c;都可以使用的指令&#xff0c;常见的有&#xff1a; - KEYS&#xff1a;查看符合模板的所有key- KEYS…

cm-14.1 Android系统启动过程分析(2)- init进程的启动之前的那些事(偏嵌入式方向)

声明 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。本文参考了一些书籍的若干章节本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译…

Golang每日一练(leetDay0038) 二叉树专题(7)

目录 112. 路径总和 Path Sum &#x1f31f; 113. 路径总和 II Path Sum II &#x1f31f;&#x1f31f; 114. 二叉树展开为链表 Flatten Binary Tree to Linked-list &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 …

Nacos作为注册中心和配置中心

Cloud依赖&#xff1a; <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><sco…