深入剖析Tomcat(六) Tomcat各组件的生命周期控制

news2024/12/23 12:25:35

Catalina中有很多组件,像上一章提到的四种容器,载入器,映射器等都是一种组件。每个组件在对外提供服务之前都需要有个启动过程;组件在销毁之前,也需要有个关闭过程;例如servlet容器关闭时,需要调用servlet的destroy方法,session管理器关闭时必须将seesion对象保存到辅助存储器中等。

Catalina为了更好的控制各组件的启动与销毁流程,做了几个生命周期的接口与工具类出来,对所有需要生命周期控制的组件做了个规范,并将启动与销毁的触发点收束到一个顶层组件中,实现统一启动或关闭所有组件。

这个统一控制的实现方式就是,由最顶层组件发号施令,一层一层往下传播,每个组件只控制其子组件的启动与关闭,直到最底层的组件。

相关的接口与类有以下几个,下面一一介绍

org.apache.catalina.Lifecycle

org.apache.catalina.LifecycleEvent

org.apache.catalina.LifecycleListener

org.apache.catalina.util.LifecycleSupport

Lifecycle接口

Catalina中的组件如果想被纳入统一生命周期管理的话,就必须实现Lifecycle接口。

该接口定义了五个方法与六个字符串属性。

最重要的方法是start()与stop()方法,这两个方法必须被实现。

start()方法中需要执行本组件的启动逻辑并调用子组件的start()方法。stop()方法需要执行本组件的关闭逻辑并调用子组件的stop()方法。Catalina的这种设计使得所有子组件都置于其父组件的“监护”之下,这样,Catalina的启动类只需要启动一个组件就可以将全部应用的组件都启动起来。

六个属性则是定义了start与stop过程中的六个阶段的事件

  • BEFORE_START_EVENT:组件启动前
  • START_EVENT:                 组件启动中
  • AFTER_START_EVENT:   组件启动后
  • BEFORE_STOP_EVENT:  组件关闭前
  • STOP_EVENT:                   组件关闭中
  • AFTER_STOP_EVENT:     组件关闭后

六种事件发生后,需要有专门的事件监听器来处理。Lifecycle接口中其他三个方法则是与事件监听器相关的方法:添加、移除、查询 事件监听器。

public interface Lifecycle {

    public static final String START_EVENT = "start";
    public static final String BEFORE_START_EVENT = "before_start";
    public static final String AFTER_START_EVENT = "after_start";
    public static final String STOP_EVENT = "stop";
    public static final String BEFORE_STOP_EVENT = "before_stop";
    public static final String AFTER_STOP_EVENT = "after_stop";

    public void addLifecycleListener(LifecycleListener listener);
    public LifecycleListener[] findLifecycleListeners();
    public void removeLifecycleListener(LifecycleListener listener);
    public void start() throws LifecycleException;
    public void stop() throws LifecycleException;

}

LifecycleEvent类

这个类是对Lifecycle中六种事件的封装,组件触发生命周期的事件后,将事件封装成LifecycleEvent对象,然后作为监听器的方法参数去触发监听。其中type字段放的就是Lifecycle中定义的六个事件之一。

public final class LifecycleEvent extends EventObject {

    public LifecycleEvent(Lifecycle lifecycle, String type) {
        this(lifecycle, type, null);
    }

    public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {
        super(lifecycle);
        this.lifecycle = lifecycle;
        this.type = type;
        this.data = data;
    }

    // 事件关联的数据
    private Object data = null;

    // 触发事件的组件
    private Lifecycle lifecycle = null;

    // 事件类型
    private String type = null;

    public Object getData() {
        return this.data;
    }

    public Lifecycle getLifecycle() {
        return this.lifecycle;
    }

    public String getType() {
        return (this.type);
    }

}

LifecycleListener接口

这是事件监听器的接口。实现了Lifecycle接口的组件,在触发了一个事件后,将事件封装成LifecycleEvent对象,调用监听器的lifecycleEvent,根据不同事件做相应处理。

package org.apache.catalina;

public interface LifecycleListener {

    /**
     * 让监听器知晓有事件发生了
     */
    public void lifecycleEvent(LifecycleEvent event);

}

LifecycleSupport类

此类是一个工具类,负责辅助 实现了Lifecycle的组件 将LifecycleEvent事件通知到所有监听器。

LifecycleSupport类有两个属性:

  • lifecycle:关联的 实现了Lifecycle接口的组件
  • listeners:lifecycle关联的监听器

addLifecycleListenerremoveLifecycleListener两个方法,通过复制监听器数组的方式来新增或移除监听器。

fireLifecycleEvent方法负责将事件封装成LifecycleEvent对象并作为参数,for循环调用所有监听器的lifecycleEvent方法,执行监听处理。

public final class LifecycleSupport {

    public LifecycleSupport(Lifecycle lifecycle) {
        super();
        this.lifecycle = lifecycle;
    }

    private Lifecycle lifecycle = null;

    private LifecycleListener[] listeners = new LifecycleListener[0];

    /**
     * 为lifecycle组件添加一个事件监听器
     */
    public void addLifecycleListener(LifecycleListener listener) {

        synchronized (listeners) {
            LifecycleListener results[] = new LifecycleListener[listeners.length + 1];
            for (int i = 0; i < listeners.length; i++) {
                results[i] = listeners[i];
            }
            results[listeners.length] = listener;
            listeners = results;
        }

    }

    public LifecycleListener[] findLifecycleListeners() {
        return listeners;
    }

    /**
     * 通知所有事件监听器,此容器发生了特定事件,默认使用调用线程以同步的方式执行此方法。
     */
    public void fireLifecycleEvent(String type, Object data) {

        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener[] interested;
        // 这里克隆一下listeners数组,我理解克隆的意义 是为了防止在执行listeners的for循环过程中,又被调用了addLifecycleListener来添加一个listener
        synchronized (listeners) {
            interested = listeners.clone();
        }
        for (int i = 0; i < interested.length; i++) {
            interested[i].lifecycleEvent(event);
        }

    }

    /**
     * 移除一个事件监听器
     */
    public void removeLifecycleListener(LifecycleListener listener) {

        synchronized (listeners) {
            int n = -1;
            for (int i = 0; i < listeners.length; i++) {
                if (listeners[i] == listener) {
                    n = i;
                    break;
                }
            }
            if (n < 0) return;
            LifecycleListener results[] = new LifecycleListener[listeners.length - 1];
            int j = 0;
            for (int i = 0; i < listeners.length; i++) {
                if (i != n) results[j++] = listeners[i];
            }
            listeners = results;
        }

    }

}

其他类

本章其他的类基本沿用了第五章的类。不同点是,本章将各个组件类都实现了Lifecycle接口。

SimpleContext最为最顶层的组件,负责“一键启停”所有组件。SimpleContext拥有一个LifecycleSupport属性,用来处理对生命周期事件的监听。本章做了一个SimpleContextLifecycleListener类来作为SimpleContext的事件监听器。在start()启动方法中,会将一个SimpleContextLifecycleListener实例与SimpleContext绑定起来。

下面是SimpleContext的部分代码

public class SimpleContext implements Context, Pipeline, Lifecycle {

    public SimpleContext() {
        pipeline.setBasic(new SimpleContextValve());
    }

    // 子容器
    protected HashMap children = new HashMap();

    // 载入器
    private Loader loader = null;

    // 生命周期支持组件
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);

    // 管道
    private SimplePipeline pipeline = new SimplePipeline(this);

    // servlet的uri映射
    private HashMap servletMappings = new HashMap();

    // 默认映射器
    protected Mapper mapper = null;

    // 映射器集合
    protected HashMap mappers = new HashMap();

    // 父组件
    private Container parent = null;

    // 此组件是否已启动
    protected boolean started = false;

    // 添加映射器
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    // 移除映射器
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }
    
    public synchronized void start() throws LifecycleException {
        if (started) {
            throw new LifecycleException("SimpleContext has already started");
        }

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
        started = true;
        try {
            // 启动所有从属组件
            if ((loader != null) && (loader instanceof Lifecycle)) {
                ((Lifecycle) loader).start();
            }

            // 启动所有子容器
            Container children[] = findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i] instanceof Lifecycle) {
                    ((Lifecycle) children[i]).start();
                }
            }

            // 启动管道中的所有阀
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }
            // 触发了特定事件,通知事件监听器
            lifecycle.fireLifecycleEvent(START_EVENT, null);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    }

    public void stop() throws LifecycleException {
        if (!started) {
            throw new LifecycleException("SimpleContext has not been started");
        }
        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;
        try {
            // 关闭管道中的所有阀
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).stop();
            }

            // 关闭所有子容器
            Container children[] = findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i] instanceof Lifecycle) {
                    ((Lifecycle) children[i]).stop();
                }
            }
            if ((loader != null) && (loader instanceof Lifecycle)) {
                ((Lifecycle) loader).stop();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    }

}

SimpleContextLifecycleListener监听器

public class SimpleContextLifecycleListener implements LifecycleListener {

    public void lifecycleEvent(LifecycleEvent event) {
        Lifecycle lifecycle = event.getLifecycle();
        System.out.println("SimpleContextLifecycleListener's event " + event.getType());
        if (Lifecycle.START_EVENT.equals(event.getType())) {
            System.out.println("Starting context.");
        } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
            System.out.println("Stopping context.");
        }
    }

}

SimpleLoader(载入器)实现了Lifecycle接口,但是方法都留空了,start与stop方法打印了一下启动信息。虽然这里start方法没有干事,但是它的启动流程已经能交给上层组件来控制了,后面再丰富启动流程也ok。

public synchronized void start() throws LifecycleException {
    System.out.println("Starting SimpleLoader");
}

public void stop() throws LifecycleException {
    System.out.println("Stopping SimpleLoader");
}

SimplePipeline实现了Lifecycle接口,但是相关方法也留空了。start与stop方法仅仅打印一条信息。

public synchronized void start() throws LifecycleException {
    System.out.println("Starting SimplePipeline");
}

public void stop() throws LifecycleException {
    System.out.println("Stopping SimplePipeline");
}

SimpleWrapper实现了Lifecycle接口,它是个容器类,包含了多种组件,所以它的start(),stop()方法做了实现。由于SimpleWrapper的每个实例都对应了一个servlet实例,所以在stop方法中,会去调用servlet的destroy方法。

public class SimpleWrapper implements Wrapper, Pipeline, Lifecycle {

    public SimpleWrapper() {
        pipeline.setBasic(new SimpleWrapperValve());
    }

    // the servlet instance
    private Servlet instance = null;
    private String servletClass;
    private Loader loader;
    private String name;
    private SimplePipeline pipeline = new SimplePipeline(this);
    protected Container parent = null;
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);
    protected boolean started = false;

    
    // 本章中的SimpleWrapper没有监听器,监听器相关的三个方法留空
    public void addLifecycleListener(LifecycleListener listener) {
    }

    public LifecycleListener[] findLifecycleListeners() {
        return null;
    }

    public void removeLifecycleListener(LifecycleListener listener) {
    }

    public synchronized void start() throws LifecycleException {
        System.out.println("Starting Wrapper " + name);
        if (started) {
            throw new LifecycleException("Wrapper already started");
        }

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
        started = true;

        // 启动所有从属组件
        if ((loader != null) && (loader instanceof Lifecycle)) {
            ((Lifecycle) loader).start();
        }

        // 启动管道中的所有阀
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    }

    public void stop() throws LifecycleException {
        System.out.println("Stopping wrapper " + name);
        // 销毁servlet实例 (如果它已经被初始化的话)
        try {
            instance.destroy();
        } catch (Throwable t) {
        }
        instance = null;
        if (!started) {
            throw new LifecycleException("Wrapper " + name + " not started");
        }
        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

        // 关闭管道中的所有阀
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).stop();
        }

        // 关闭所有从属组件
        if ((loader != null) && (loader instanceof Lifecycle)) {
            ((Lifecycle) loader).stop();
        }

        // 触发了特定事件,通知事件监听器
        lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
    }
}

启动类与第五章相比,还是原来的配方,还是原来的味道。

public final class Bootstrap {
    public static void main(String[] args) {
        Connector connector = new HttpConnector();
        Wrapper wrapper1 = new SimpleWrapper();
        wrapper1.setName("Primitive");
        wrapper1.setServletClass("PrimitiveServlet");
        Wrapper wrapper2 = new SimpleWrapper();
        wrapper2.setName("Modern");
        wrapper2.setServletClass("ModernServlet");

        Context context = new SimpleContext();
        context.addChild(wrapper1);
        context.addChild(wrapper2);

        Mapper mapper = new SimpleContextMapper();
        mapper.setProtocol("http");
        LifecycleListener listener = new SimpleContextLifecycleListener();
        ((Lifecycle) context).addLifecycleListener(listener);
        context.addMapper(mapper);
        Loader loader = new SimpleLoader();
        context.setLoader(loader);
        // context.addServletMapping(pattern, name);
        context.addServletMapping("/Primitive", "Primitive");
        context.addServletMapping("/Modern", "Modern");
        connector.setContainer(context);
        try {
            connector.initialize();
            ((Lifecycle) connector).start();
            ((Lifecycle) context).start();

            // make the application wait until we press a key.
            System.in.read();
            ((Lifecycle) context).stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

启动应用,看后端日志

HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
SimpleContextLifecycleListener's event before_start
Starting SimpleLoader
Starting Wrapper Primitive
Starting SimplePipeline
Starting Wrapper Modern
Starting SimplePipeline
Starting SimplePipeline
SimpleContextLifecycleListener's event start
Starting context.
SimpleContextLifecycleListener's event after_start
关闭应用
SimpleContextLifecycleListener's event before_stop
SimpleContextLifecycleListener's event stop
Stopping context.
Stopping SimplePipeline
Stopping wrapper Primitive
Stopping SimplePipeline
Stopping wrapper Modern
Stopping SimplePipeline
Stopping SimpleLoader
SimpleContextLifecycleListener's event after_stop

Process finished with exit code 0

可以看到,随着SimpleContext#start()方法的调用,其下属的各组件和子容器都被启动了起来。而当我往控制台中输入了内容(“关闭应用”)之后,触发关闭流程,随着SimpleContext#stop()方法的调用,其下属各组件与子容器也都被关闭了。由于SimpleContext与SimpleWraper容器实例中都包含SimplePipeline所以你能看到多条SimplePipeline的启动信息。

好,本章内容到此结束。Tomcat通过Lifecycle这么一组接口,将各个组件的生命周期串联了起来,实现了“一键启动”、“一键关闭”。这个思想值得借鉴,体悟这种编程思想,也为我们以后的编程场景多一种参考储备。下一章,我们一起来看看Tomcat中的日志记录器,看看它是怎么记日志的,敬请期待吧!

源码分享

https://gitee.com/huo-ming-lu/HowTomcatWorks

原书中的代码没有明显bug,所以我仅仅格式化了一下代码,并加了些注释

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

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

相关文章

数据结构——图的基础知识与其表示

一&#xff1a;定义 由顶点的集合和边的集合组成&#xff1b;常以 G(V,E) 表示&#xff0c;G 代表图&#xff0c;V代表 顶点的集合&#xff0c;E代表边的集合&#xff1b; 如图&#xff1a; 在G1图中&#xff0c;有 0~4 五个顶点&#xff0c;有 0-1&#xff0c;0-2&…

文献速递:深度学习医学影像心脏疾病检测与诊断--从SPECT/CT衰减图中深度学习冠状动脉钙化评分提高了对重大不良心脏事件的预测

Title 题目 Deep Learning Coronary Artery Calcium Scores from SPECT/CT Attenuation Maps Improve Prediction of Major Adverse Cardiac Events 从SPECT/CT衰减图中深度学习冠状动脉钙化评分提高了对重大不良心脏事件的预测 01 文献速递介绍 低剂量非门控CT衰减校正&am…

Problem 5: Whack-A-Mole打地鼠

实战题&#xff1a;打地鼠 内容如附件所示&#xff1a; 测试数据为:1,2,4,8,9,10,11,14 答案为&#xff1a;10,2,4 原始分布&#xff1a; 击打10号 击打2号 击打4号 要求&#xff0c;所示实例解以图示的方式给出&#xff0c;并且5组测试数据都需要测试&#xff0c;…

力扣每日一练(螺旋矩阵)

54. 螺旋矩阵 - 力扣&#xff08;LeetCode&#xff09; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,…

获取转转数据,研究完转转请求,tx在算法方面很友好。

本篇文章仅供学习讨论。 文章中涉及到的代码、实例&#xff0c;仅是个人日常学习研究的部分成果。 如有不当&#xff0c;请联系删除。 在研究完阿里的算法以后&#xff08;其实很难说研究完&#xff0c;还有很多内容没有研究透&#xff0c;只能说暂时告一段落&#xff09;&…

关于IDEA中项目中各个方法、引用、注解等全部报错的情况

今天打开项目弹出很多提示框&#xff0c;也没注意&#xff0c;然后突然发现项目所有都在报错&#xff0c;不管是启动类还是方法类&#xff0c;各种注解、方法、引用等全红了&#xff0c;随便打开一个都是密密麻麻全红。 首先排查依赖和JDK等引用问题&#xff0c;包括我们的mave…

jquery项目 html使用export import方式调用模块

jquery的老项目&#xff0c;引入vue3, 需要方便使用export, import方式引用一些常用的方法与常量 导出模块 export js/numberUtil.js /*** Description:* Author Lani* date 2024/1/10*//* * 【金额】 保留2位小数&#xff0c;不四舍五入 * 5.992550 >5.99 , 2 > 2.…

力扣:63. 不同路径 II

63. 不同路径 II 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物。那么…

C++:多继承虚继承

在C中&#xff0c;虚继承&#xff08;Virtual Inheritance&#xff09;是一种特殊的继承方式&#xff0c;用于解决菱形继承&#xff08;Diamond Inheritance&#xff09;问题。菱形继承指的是一个类同时继承自两个或更多个具有共同基类的类&#xff0c;从而导致了多个实例同一个…

Apache POI入门学习

Apache POI入门学习 官网地址 excel中使用到的类读取excel表格内容表格内容maven依赖方式一测试结果 方式二测试结果 向excel中写入数据方式一方式二方式三测试结果 从 Excel 工作表中的公式单元格读取数据测试结果 Excel 工作表中写入公式单元格从受密码保护的Excel中读取数据…

opencv图片的平移-------c++

图片平移 cv::Mat opencvTool::translateImage(const cv::Mat& img, int dx, int dy) {// 获取图像尺寸int rows img.rows;int cols img.cols;// 定义仿射变换矩阵cv::Mat M (cv::Mat_<float>(2, 3) << 1, 0, dx, 0, 1, dy);// 进行仿射变换cv::Mat dst;cv…

Linux-信号概念

1. 什么是信号 信号本质是一种通知机制&#xff0c;用户or操作系统通过发送信号通知进程&#xff0c;进程进行后续处理 在日常生活中就有很多例子&#xff0c;比如打游戏方面王者荣耀的“进攻”&#xff0c;“撤退”&#xff0c;“请求集合”&#xff0c;“干得漂亮&#xff01…

第一篇:刚接触测试你应该知道什么

欢迎你接触软件测试这一行业。 刚接触它时&#xff0c;你肯定或多或少会有疑惑&#xff0c;我该做什么&#xff1f;大家口口相传的软件测试就是 【点点点】 真的是你日常的工作吗&#xff1f; 那么本文我将陪你一起&#xff0c;对我们刚接触到测试这个工作以后&#xff0c;应该…

第七节课《OpenCompass司南--大模型评测实战》

OpenCompass 大模型评测实战_哔哩哔哩_bilibili https://github.com/InternLM/Tutorial/blob/camp2/opencompass/readme.md InternStudio 一、通过评测促进模型发展 面向未来拓展能力维度&#xff1a;评测体系需增加新能力维度&#xff08;数学、复杂推理、逻辑推理、代码和…

[SUCTF 2019]CheckIn 1

解题步骤 上传木马图片&#xff0c;命名为b.php GIF89a <script languagephp>eval($_POST[cmd])</script>bp抓包&#xff0c;修改数据&#xff1b;然后可看到上传的文件 上传.user.ini文件&#xff0c;内容编写如下&#xff0c;然后bp抓包修改文件类型 GIF8…

MOS产品在光伏逆变器上的应用和产品选型

2023年全球光伏装机量表现优异&#xff0c;根据BloombergNEF统计数据&#xff0c;2023年全球光伏新增装机量444GW&#xff0c;同比增长76.2%&#xff0c;其中约一半新增装机量来自中国。 中国光伏新技术迭代不断&#xff0c;产业链降本增效加速。根据CPIA数据&#xff0c;2022年…

简述 BIO 、NIO 模型

BIO : 同步阻塞I/O&#xff08;Block IO&#xff09; 服务器实现模式为每一个连接一个线程&#xff0c;即客户端有连接请求时服务器就需要启动一个线程进行处理&#xff0c;如果这个连接不做任何事情会造成不必要的线程开销&#xff0c;此处可以通过线程池机制进行优化。 impo…

新的项目springboot

buybuyshenglombok <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency> 添加依赖 lombok package com.example.demo.pojo;import lombok.AllArgsConstructor; import lombok.Data; import …

【机器学习与实现】线性回归分析

目录 一、相关和回归的概念&#xff08;一&#xff09;变量间的关系&#xff08;二&#xff09;Pearson&#xff08;皮尔逊&#xff09;相关系数 二、线性回归的概念和方程&#xff08;一&#xff09;回归分析概述&#xff08;二&#xff09;线性回归方程 三、线性回归模型的损…

BigDecimal:踩坑

问题描述 两个BigDecimal相除, 抛了异常 原因分析&#xff1a; Java 中使用 BigDecimal 做除法运算的时候&#xff0c;值有可能是无限循环的小数&#xff0c;结果是无限循环的小数&#xff0c;就会抛出上面这个异常。 来看看源码&#xff1a; public BigDecimal divide(BigD…