Tomcat源码解析——类加载机制

        一、类加载器的创建

        在之前的Tomcat启动源码中,简单的介绍了Tomcat的四种类加载器,再复习一遍。

类加载器

作用父加载器
commonLoader(共同类加载器)加载$CATALINA_HOME/lib下的类加载器应用类加载器
catalinaLoader(容器类加载器)加载Tomcat应用服务器的类加载器,可以理解为加载Tomcat源码中的类共同类加载器
sharedLoader(共享类加载器)加载应用类加载器的共享的类加载器,例如相同版本的mysql驱动等共同类加载器
webappLoader(Web应用类加载)加载Web应用下的类类加载,每个web应用之间是相互隔离的共享类加载器

        类加载器的结构层次:

                

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

        在Tomcat的启动中,一开始就创建了commonLoader、catalinaLoader、sharedLoader类加载器并且加载对应设置的资源。

Bootstrap:
   private void initClassLoaders() {
        try {
            //读取catalina.properties下的common.loader资源加载
            //commonLoader的父类加载器是应用类加载器(不设置都默认为应用类加载器)
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                commonLoader=this.getClass().getClassLoader();
            }
            //读取catalina.properties下的server.loader资源加载
            //catalinaLoader的父类加载器是commonLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            //读取catalina.properties下的shard.loader资源加载
            //sharedLoader的父类加载器是commonLoader
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        //读取catalina.properties中对应的key值
        String value = CatalinaProperties.getProperty(name + ".loader");
        //如果没有设值对应的属性值,则返回父类加载器
        if ((value == null) || (value.equals("")))
            return parent;
        //替换掉Tomact的表达式变量值,如catalina.home、catalina.base等为具体的路径
        value = replace(value);
        List<Repository> repositories = new ArrayList<Repository>();
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken().trim();
            if (repository.length() == 0) {
                continue;
            }
            try {
                //创建一个URL
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(
                        new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        //创建一个URL的类加载器,并把要加载的路径传递过去
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

        严格来说,commonLoader和sharedLoader都算是可以共享的类加载器,只是作用域不同,所以在catalina.properties中设置了common.loader和shared.loader加载路径时,则在应用的maven中需要排除掉这些共享的依赖(不排除的话,会在web类加载器中重复加载,common.loader和server.loader设置就无意义)。

        commonLoader是catalinaLoader和sharedLoader的父类加载器,如果server.loader和shared.loader没有设值时,那么这3个类加载器的值都是一样的。

        Web应用类加载器的创建是在Host容器启动之后,Host容器会发送事件到HostConfig中,然后启动Host下的应用。

HostConfig:
    public void lifecycleEvent(LifecycleEvent event) {
        try {
            //Host的生命周期事件
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            //Host启动之后,部署启动Host下的应用
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }


    public void start() {
        //...省略
        if (host.getDeployOnStartup())
            //部署应用,即启动应用
            deployApps();

    }

    protected void deployApps() {
        File appBase = appBase();
        File configBase = configBase();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        deployDescriptors(configBase, configBase.list());
        //热部署War包
        deployWARs(appBase, filteredAppPaths);
        //部署扩展文件夹,即War包的解压
        deployDirectories(appBase, filteredAppPaths);
    }

        不管是如何部署,最终都会调用Context的start方法启动应用,在Context启动时,会创建对应的Web应用类加载器进行绑定,即一个Context对应一个Web应用类加载器,从而实现应用之间Jar包的隔离。

        

WebappLoader:
    private String loaderClass =
        "org.apache.catalina.loader.WebappClassLoader";

    protected void startInternal() throws LifecycleException {
        //...省略
        try {
            //创建应用对应的Web应用类加载器
            classLoader = createClassLoader();
            classLoader.setResources(container.getResources());
            classLoader.setDelegate(this.delegate);
            classLoader.setSearchExternalFirst(searchExternalFirst);
            if (container instanceof StandardContext) {
                classLoader.setAntiJARLocking(
                        ((StandardContext) container).getAntiJARLocking());
                classLoader.setClearReferencesStatic(
                        ((StandardContext) container).getClearReferencesStatic());
                classLoader.setClearReferencesStopThreads(
                        ((StandardContext) container).getClearReferencesStopThreads());
                classLoader.setClearReferencesStopTimerThreads(
                        ((StandardContext) container).getClearReferencesStopTimerThreads());
                classLoader.setClearReferencesHttpClientKeepAliveThread(
                        ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
            }
        //...省略
    }

    private WebappClassLoaderBase createClassLoader()
        throws Exception {

        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;

        if (parentClassLoader == null) {
            parentClassLoader = container.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);

        return classLoader;

    }

        可以看到,默认创建的Web应用类加载器是WebappClassLoader,同时会设置父类加载。

        通过打断点的方式,可以看到Web应用类加载器的父类加载器是共享类加载器。

        二、类加载器的使用

        在上文中,Tomcat的四大类加载器已经创建完毕,那么Tomcat是如何实现应用之间的隔离?

        在上一篇Tomcat的请求流程中,我们知道当一个请求到达时,会经过容器通过Valve传递。

StandardHostValve:
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        //...省略
        //为当前线程设置应用的类加载器,从而实现应用之间隔离
        Thread.currentThread().setContextClassLoader
                        (context.getLoader().getClassLoader());
        //...省略
        //传递到下一个容器的Valve中
        context.getPipeline().getFirst().invoke(request, response)
    }

        当请求到达HostValve中,会为当前线程设置类加载器为对应的Web应用类加载器,那么JVM在后面加载类时,会使用该类加载器。(JVM的机制)

        WebappClassLoader是默认的Web应用类加载器,重写了loadClass方法。

WebappClassLoader:

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLockInternal(name)) {
            Class<?> clazz = null;
            //先查看本地缓存是否加载了该类
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
            //查询当前的类加载器是否已经加载了
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
            //尝试用扩展类加载器加载该类(依旧遵循双亲委派机制),防止Java的核心类被破坏
            try {
                clazz = j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
            }
            //检查访问权限
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }
            boolean delegateLoad = delegate || filter(name);
            //当delegate为true时,会继续使用双亲委派加载方式,不过默认都是false
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                }
            }
            try {
                //查找加载应用目录的class(/WEB-INF/classes和/WEB-INF/lib,既war包中的所有类)
                clazz = findClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
            }
            //如果本地的class还没加载没有找到,则使用父加载器加载继续尝试(此处会走双亲委派)
            if (!delegateLoad) {
                try {
                    //使用父加载器加载,此处会走双亲委派的机制
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                }
            }
        }
        
        throw new ClassNotFoundException(name);
    }

        应用类加载器的加载流程:

                1.查询缓存是否已经加载,加载不成功则进行下一步

                2.使用扩展类加载器加载(防止Java核心类被破坏),加载不成功则进行下一步

                3.使用Web应用类加载器加载(加载/WEB-INF/classes和/WEB-INF/lib),加载不成功则进行下一步

                4.交给父类加载器走双亲委派加载,加载路径则为:共享类加载器——>共同类加载器——>应用类加载器——>扩展类加载器——>系统类加载器      

        每个应用都使用自己的Web应用类加载器加载/WEB-INF/classes和/WEB-INF/lib,从而实现了Tomcat之间的应用隔离。

三、为什么要打破双亲委派机制?

        如果不打破双亲委派机制,能实现应用之间的隔离吗?

        答案是可以的,如果每个应用都创建一个自己的类加载器,走双亲委派加载时,最终还是在该类加载器实现最终的加载。

        既然如此,为什么Tomcat要打破双亲委派机制呢?

        因为Tomcat需要节约资源,如果走了双亲委派机制,那么一些共同的类库将无法实现共享,每个应用的类加载器都需要把所有的类库全部加载到自己的类加载器中,会浪费很多的内存资源,打破双亲委派机制,不仅可以让共同使用的类库实现共享,还能实现应用之间的隔离,不造成内存资源的浪费。

        

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CAD小软件diy-读柴油机壳体装配图

读取一个柴油机壳体dxf图纸&#xff0c;一般这种装配体轮廓曲线都是用直线和圆弧拟合的&#xff0c;全部都是显示的白色实现&#xff0c;发现有线段间隙&#xff0c;拖动线段补上间隙。 这个测试放在蓝奏云上面 https://wwf.lanzout.com/ip1Xx1vvhbkh

08 SQL进阶 -- 集合运算 -- 表的连结(JOIN)

1. 连结(JOIN) 前一节我们学习了 UNION和INTERSECT 等集合运算, 这些集合运算的特征就是以行方向为单位进行操作. 通俗地说, 就是进行这些集合运算时, 会导致记录行数的增减。使用 UNION 会增加记录行数,而使用 INTERSECT 或者 EXCEPT 会减少记录行数。 但这些运算不能改变…

张大哥笔记:到底什么是轻创业?怎么才叫轻创业

大家好&#xff0c;我是张大哥&#xff0c;我在公众号反复强调&#xff0c;个人创业尽量去选择轻资产项目&#xff0c;要么不创业&#xff0c;要么轻创业&#xff01;到底什么是轻创业&#xff1f;怎么才叫轻创业呢&#xff0c;本问为你揭晓&#xff1a; 刚开始创业&#xff0c…

nginx--Nginx转发真实的IP

Nginx转发真实的IP 前言给nginx.conf 设置proxy_set_headerjava 程序里获取 前言 在使用nginx的时候可能会遇到判断是不是本机在做操作&#xff0c;这样的话web端我们是可以通过ip和端口进行远程连接的这样的话我们就需要从后端获取到真实ip来判断是不是指定的机器了&#xff…

2023androidstudio

终于下定决心将studio升级到新版本使用了&#xff0c;在这总结下和之前的差别 问题一&#xff1a; 创建java类型的项目 在新版本studio中&#xff0c;创建android项目时&#xff0c;语言选择中没有java选项了&#xff0c;这让一直使用java开发的我摸索了好久&#xff0c;终于…

深入剖析图像平滑与噪声滤波

噪声 在数字图像处理中&#xff0c;噪声是指在图像中引入的不希望的随机或无意义的信号。它是由于图像采集、传输、存储或处理过程中的各种因素引起的。 噪声会导致图像质量下降&#xff0c;使图像失真或降低细节的清晰度。它通常表现为图像中随机分布的亮度或颜色变化&#…

不敢说懂你 - Glide硬核源码剖析

问题 Glide加载流程? Glide整体架构? Glide数据加载的来源? Glide缓存加载的流程? Glide线程切换原理? Glide如何感知Activity? Glide哪种情况会返回应用级的RequestManager? … 带着一些问题去阅读… 使用示例 本篇主要基于glide:4.12.0进行分析。下面是Gli…

LeetCode 11.盛最多谁的容器

目录 题目描述 方法一 双指针 思路&#xff1a; 代码&#xff1a; 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的…

实验室三大常用仪器1---示波器的基本使用方法(笔记)

目录 示波器的作用 示波器的基础操作方法 示波器测量突变脉冲 示波器的作用 示波器能帮助我们干什么&#xff1f; 比如说某个电源用万用表测量是稳定的5V输出 但是用示波器一看确实波涛汹涌 这样的电源很可能回导致系统异常工作 又比如电脑和单片机进行串口通信时&#xf…

ubuntu在xshell中使用快捷方式操作命令,减少命令行的数入量

第一步 第二步 然后无脑确定 第三步 在xshell的显示方式 方式一 这样就会在每个窗格中进行显示 方式二 效果显示–> 这种窗格的显示是全局的 然后你双击这个process就会自动把命令打在命令行上&#xff0c;减少你的输入量

如何在本地服务器部署TeslaMate

文章目录 1.主要参考官方文档2.准备文件&#xff1a;docker-compose.yml3.运行4.成功后4.1 在这个链接&#xff0c;更具提示登录4.2 在这个链接可以看到电池健康和行车数据等 5.后续说明6.进行数据备份6.1 先将数据进行备份&#xff0c;参考链接6.2 数据迁移6.3 下图为我挂该数…

布隆过滤器初探

1、什么是布隆过滤器 布隆过滤器是一个很长的二进制向量和一系列随机hash函数。布隆过滤器可以用于检索一个元素是否在一个集合中。 常见的hash函数的应用hashMap、hashSet等 回顾一下hashMap的结构 hashMap由数组链表红黑树&#xff08;java1.8后&#xff0c;链表元素长度大…

七月论文审稿GPT第4.5版:通过15K条paper-review数据微调Llama2 70B(含各种坑)

前言 当我们3月下旬微调完Mixtral 8x7B之后(更多详见&#xff1a;七月论文大模型&#xff1a;含论文的审稿、阅读、写作、修订 )&#xff0c;下一个想微调的就是llama2 70B 因为之前积攒了不少微调代码和微调经验&#xff0c;所以3月底apple便通过5K的paper-review数据集成功…

xilinx cpri ip 开发记录

CPRI是无线通信里的一个标准协议&#xff0c;连接REC和RE的通信。 Xilinx有提供CPRI IP核。 区别于其它通信协议&#xff0c;如以太网等&#xff0c;CPRI是一个同步系统。 这就意味着两端的Master和Slave应当是同源时钟的&#xff0c;两边不存在频差&#xff0c;并且内部延时…

使用isort和autopep8统一代码风格

前言 今天和大家分享一篇关于python代码风格统一的方法。我自己之前有使用过&#xff0c;但都是使用公司现成的&#xff0c;没有自己动手去实操&#xff0c;所以为了一探究竟&#xff0c;今天专门花了一点时间去研究&#xff0c;这个过程还挺顺利的&#xff0c;这里我将这个过…

什么是IIoT?

什么是IIoT? IIoT,即工业物联网(Industrial Internet of Things),是指将物联网技术应用到工业领域,通过微型低成本传感器、高带宽无线网络等技术手段,实现工业设备、系统和服务的互联互通,从而提高生产效率、降低能耗和成本,实现智能化和自动化生产。 IIoT的应用范围…

Vitis HLS 学习笔记--BLAS库之WideType

目录 1. WideType 数据类型 2. WideType 类模板参数 2.1 SFINAE技术 3. WideType 类中的函数 3.1 operator[](unsigned int p_Idx) 3.2 operator(const WideType& p_w) const 3.3 getValAddr() 3.4 operator const t_TypeInt() 4. 总结 1. WideType 数据类型 在 …

NtripShare2024年第一季度主要技术进展

迷迷糊糊又是一个月没有写点什么&#xff0c;近期想清楚NtripShare在2024的要做什么事情&#xff0c;暂且将NtripShare要做的主要事情为搭建由软件与硬件之间的技术桥梁。 在过去的几年时间里NtripShare对硬件方面一直是规避的态度&#xff0c;今年开始要做一点软硬件搭界的技…

网络编程初步

协议&#xff1a; 一组规则 分层模型结构&#xff1a; OSI七层模型&#xff1a;物、数、网、传、会、表、应 TCP/IP 4层模型&#xff1a;网&#xff08;链路层/网络接口层)、网、传、应 应用层&#xff1a;http、 ftp、 nfs、 ssh、 telneto o .传输层:TCP、UDP 网络层&…

SpringBoot基于JavaWeb的菜鸟驿站快递管理系统ssm

前端&#xff1a;vue.jsElementUI 编程语言: java 框架&#xff1a; ssm/springboot 详细技术&#xff1a;springboot springbootvueMYSQLMAVEN 数据库: mysql5.7 数据库工具&#xff1a;Navicat/SQLyog都可以 ide工具&#xff1a;IDEA 或者eclipse 对菜鸟驿站快递管理系统设计…
最新文章