精华内容
下载资源
问答
  • tomcat打破双亲委派机制
    2021-01-16 00:49:00

    tomcat在jvm提供的类加载器上进行了扩展,并且打破了双亲委托机制

    CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/*、/server/*、/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

    commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;

    catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;

    sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;

    WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

    从图中的委派关系中可以看出:

    CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。

    WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

    而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

    好了,至此,我们已经知道了tomcat为什么要这么设计,以及是如何设计的,那么,tomcat 违背了java 推荐的双亲委派模型了吗?答案是:违背了。我们前面说过:

    双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。

    很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

    更多相关内容
  • Tomcat打破双亲委派机制

    千次阅读 2021-11-30 13:22:39
    沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载自己实现的 java.lang.String.class public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String ...

    打破双亲委派

    沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载自己实现的 java.lang.String.class

    public class MyClassLoaderTest {
        static class MyClassLoader extends ClassLoader {
            private String classPath;
    
            public MyClassLoader(String classPath) {
                this.classPath = classPath;
            }
    
            private byte[] loadByte(String name) throws Exception {
                name = name.replaceAll("\\.", "/");
                FileInputStream fis = new FileInputStream(classPath + "/" + name
                        + ".class");
                int len = fis.available();
                byte[] data = new byte[len];
                fis.read(data);
                fis.close();
                return data;
    
            }
    
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    byte[] data = loadByte(name);
                    return defineClass(name, data, 0, data.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
    
            /**
             * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
             * @param name
             * @param resolve
             * @return
             * @throws ClassNotFoundException
             */
            protected Class<?> loadClass(String name, boolean resolve)
                    throws ClassNotFoundException {
                synchronized (getClassLoadingLock(name)) {
                    // First, check if the class has already been loaded
                    Class<?> c = findLoadedClass(name);
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }
        }
    
        public static void main(String args[]) throws Exception {
            MyClassLoader classLoader = new MyClassLoader("D:/test");
            //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
            Class clazz = classLoader.loadClass("java.lang.String");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());
        }
    }
    
    运行结果:
    java.lang.SecurityException: Prohibited package name: java.lang
    	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
    	at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
    

    Tomcat打破双亲委派机制

    以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
    Tomcat是个web容器, 那么它要解决什么问题:

    1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
    3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
    4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

    Tomcat 如果使用默认的双亲委派类加载机制行不行?
    答案是不行的。为什么?
    第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
    第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
    第三个问题和第一个问题一样。
    再看第四个问题,要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
    以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
    我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

    1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
    3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
    4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

    再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行?
    答案是不行的。为什么?
    第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
    第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
    第三个问题和第一个问题一样。
    我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

    Tomcat自定义加载器详解

    在这里插入图片描述
    Tomcat的几个主要类加载器
    commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个webapp访问
    catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class不可以为webapp可见
    **shareLoader:**各个webapp共享的类加载器,加载路径中的class可以被所有的webapp访问,但是对于Tomcat容器不可见
    **WebappClassLoader:**各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本

    tomcat 这种类加载机制违背了java 推荐的双亲委派模型了吗?答案是:违背了。
    很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。

    模拟实现Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离

    public class MyClassLoaderTest {
        static class MyClassLoader extends ClassLoader {
            private String classPath;
    
            public MyClassLoader(String classPath) {
                this.classPath = classPath;
            }
    
            private byte[] loadByte(String name) throws Exception {
                name = name.replaceAll("\\.", "/");
                FileInputStream fis = new FileInputStream(classPath + "/" + name
                        + ".class");
                int len = fis.available();
                byte[] data = new byte[len];
                fis.read(data);
                fis.close();
                return data;
    
            }
    
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    byte[] data = loadByte(name);
                    return defineClass(name, data, 0, data.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
    
            /**
             * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
             * @param name
             * @param resolve
             * @return
             * @throws ClassNotFoundException
             */
            protected Class<?> loadClass(String name, boolean resolve)
                    throws ClassNotFoundException {
                synchronized (getClassLoadingLock(name)) {
                    // First, check if the class has already been loaded
                    Class<?> c = findLoadedClass(name);
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
    
                        //非自定义的类还是走双亲委派加载
                        if (!name.startsWith("com.tuling.jvm")){
                            c = this.getParent().loadClass(name);
                        }else{
                            c = findClass(name);
                        }
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }
        }
    
        public static void main(String args[]) throws Exception {
            MyClassLoader classLoader = new MyClassLoader("D:/test");
            Class clazz = classLoader.loadClass("com.jvm.User1");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader());
            
            System.out.println();
            MyClassLoader classLoader1 = new MyClassLoader("D:/test1");
            Class clazz1 = classLoader1.loadClass("com.jvm.User1");
            Object obj1 = clazz1.newInstance();
            Method method1= clazz1.getDeclaredMethod("sout", null);
            method1.invoke(obj1, null);
            System.out.println(clazz1.getClassLoader());
        }
    }
    
    运行结果:
    =======自己的加载器加载类调用方法=======
    com.jvm.MyClassLoaderTest$MyClassLoader@266474c2
    
    =======另外一个User1版本:自己的加载器加载类调用方法=======
    com.jvm.MyClassLoaderTest$MyClassLoader@66d3c617
    

    注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。

    模拟实现Tomcat的JasperLoader热加载

    原理:后台启动线程监听jsp文件变化,如果变化了找到该jsp对应的servlet类的加载器引用(gcroot),重新生成新的JasperLoader加载器赋值给引用,然后加载新的jsp对应的servlet类,之前的那个加载器因为没有gcroot引用了,下一次gc的时候会被销毁。

    User类的代码:

    package com.jvm;
    
    public class User {
    
        private int id;
        private String name;
        
        public User() {
        }
    
        public User(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void sout() {
            System.out.println("=======自己的加载器加载类调用方法=======");
        }
    }
    
    展开全文
  • Tomcat如何打破双亲委派机制Tomcat是一个Web框架,并且可以支持部署多个web项目,web项目在Tomcat被抽象的称为Context,即每一个web项目是一个Context。而每个Context是独立的,比如项目A可以引用spring1.0,而...

    Tomcat如何打破双亲委派机制?

    Tomcat是一个Web框架,并且可以支持部署多个web项目,web项目在Tomcat被抽象的称为Context,即每一个web项目是一个Context。而每个Context是独立的,比如项目A可以引用spring1.0,而项目B可以引用spring2.0版本,如果是双亲委派机制,那么只能存在一个spring版本。Tomcat是如何打破双亲委派机制的呢?

    什么是类加载?

    Java 的类加载,就是把字节码格式“.class”文件加载到 JVM 的方法区,并在 JVM 的堆区建立一个java.lang.Class对象的实例,用来封装 Java 类相关的数据和方法。那 Class 对象又是什么呢?你可以把它理解成业务类的模板,JVM 根据这个模板来创建具体业务类对象实例。

    JVM 并不是在启动时就把所有的“.class”文件都加载一遍,而是程序在运行过程中用到了这个类才去加载。JVM 类加载是由类加载器来完成的。

    双亲委派机制

    首先我们来复习一下双亲委派机制:

    image-20210802131906207

    • BootstrapClassLoader 是启动类加载器,由 C 语言实现,用来加载 JVM 启动时所需要的核心类,比如rt.jarresources.jar等。
    • ExtClassLoader 是扩展类加载器,用来加载\jre\lib\ext目录下 JAR 包。
    • AppClassLoader 是系统类加载器,用来加载 classpath 下的类,应用程序默认用它来加载类。
    • 自定义类加载器,用来加载自定义路径下的类。

    简单描述下:

    每个类加载器负责一部分指定类的加载。若遇到某个类,需要加载,并不直接去加载,先让父加载器去查询是否加载过,若父类加载器加载过,则返回结束,否则让父类加载器尝试加载,若不是当前父类负责的加载内容,则向下返回给子类,让子类自己尝试加载。

    这样的好处:

    1. 避免重复加载(class的唯一性)
    2. 防止破坏已有的一些系统的class

    而Tomca t每个Context可能会存在相同全限定类名,比如项目A中有个类叫com.add.domain.User而项目B中也可能有个类叫com.add.domain.User。这样的情况肯定是要将两个类都加载,并且要实现项目之间的独立。或者是项目A和B都依赖了一个spring,但是版本是不同的,这样也需要都加载,并且要实现项目之间的独立。

    JVM类加载源码分析

    现在Java如果不搞个源码分析,都找不到工作了

    首先要明确,上面双亲委派机制体系中,不是继承的关系,而是父子关系,是用一个指针指向父类加载器实现的,具体是实现ClassLoader类。我们来看看默认的双亲委派机制的类加载源码。

    public abstract class ClassLoader {
     
        // 每个类加载器都有个父加载器
        private final ClassLoader parent;
        
        public Class<?> loadClass(String name) {
      
            // 查找一下这个类是不是已经加载过了
            Class<?> c = findLoadedClass(name);
            
            // 如果没有加载过
            if( c == null ){
              // 先委托给父加载器去加载,注意这是个递归调用
              if (parent != null) {
                  c = parent.loadClass(name);
              }else {
                  // 如果父加载器为空,查找 Bootstrap 加载器是不是加载过了
                  c = findBootstrapClassOrNull(name);
              }
            }
            // 如果父加载器没加载成功,调用自己的 findClass 去加载
            if (c == null) {
                c = findClass(name);
            }
            
            return c;
        }
        
        protected Class<?> findClass(String name){
           //1. 根据传入的类名 name,到在特定目录下去寻找类文件,把.class 文件读入内存
              ...
              
           //2. 调用 defineClass 将字节数组转成 Class 对象
           return defineClass(buf, off, len)}
        
        // 将字节码数组解析成一个 Class 对象,用 native 方法实现
        protected final Class<?> defineClass(byte[] b, int off, int len){
           ...
        }
    }
    
    • findClass方法是找到自己负责的区域的.class文件,并且调用JVM提供的方法将class装载进内存,并且得到Class对象
    • loadClass方法是public,即对外提供服务的接口。首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。请你注意,这是一个递归调用,也就是说子加载器持有父加载器的引用,当一个类加载器需要加载一个 Java 类时,会先委托父加载器去加载,然后父加载器在自己的加载路径中搜索 Java 类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制的实现。

    如果我们想要自定义类加载器,是继承ClassLoader 抽象类,再重写 findClass 和 loadClass 方法即可。

    Tomcat类加载器

    Tomcat自定义的类加载器WebAppClassLoader 打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader 的两个方法:findClass 和 loadClass。

    findClass方法

    public Class<?> findClass(String name) throws ClassNotFoundException {
        ...
        
        Class<?> clazz = null;
        try {
                //1. 先在 Web 应用目录下查找类 
                clazz = findClassInternal(name);
        }  catch (RuntimeException e) {
               throw e;
           }
        
        if (clazz == null) {
        try {
                //2. 如果在本地目录没有找到,交给父加载器去查找
                clazz = super.findClass(name);
        }  catch (RuntimeException e) {
               throw e;
           }
        
        //3. 如果父类也没找到,抛出 ClassNotFoundException
        if (clazz == null) {
            throw new ClassNotFoundException(name);
         }
     
        return clazz;
    }
    

    findClass方法大致步骤:

    1. 先在 Web 应用本地目录下查找要加载的类。
    2. 如果没有找到,交给父加载器去查找,它的父加载器就是上面提到的系统类加载器 AppClassLoader。
    3. 如何父加载器也没找到这个类,抛出 ClassNotFound 异常。

    loadClass 方法

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
     
        synchronized (getClassLoadingLock(name)) {
     
            Class<?> clazz = null;
     
            //1. 先在本地 cache 查找该类是否已经加载过
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
     
            //2. 从系统类加载器的 cache 中查找是否加载过
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
     
            // 3. 尝试用 ExtClassLoader 类加载器类加载,为什么?
            ClassLoader javaseLoader = getJavaseClassLoader();
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
     
            // 4. 尝试在本地目录搜索 class 并加载
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
     
            // 5. 尝试用系统类加载器 (也就是 AppClassLoader) 来加载
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
           }
        
        //6. 上述过程都加载失败,抛出异常
        throw new ClassNotFoundException(name);
    }
    

    loadClass方法大致如下:

    1. 先在本地 Cache 查找该类是否已经加载过,也就是说 Tomcat 的类加载器是否已经加载过这个类。
    2. 如果 Tomcat 类加载器没有加载过这个类,再看看系统类加载器是否加载过。
    3. 如果都没有,就让ExtClassLoader去加载,这一步比较关键,目的防止 Web 应用自己的类覆盖 JRE 的核心类。因为 Tomcat 需要打破双亲委托机制,假如 Web 应用里自定义了一个叫 Object 的类,如果先加载这个 Object 类,就会覆盖 JRE 里面的那个 Object 类,这就是为什么 Tomcat 的类加载器会优先尝试用 ExtClassLoader 去加载,因为 ExtClassLoader 会委托给 BootstrapClassLoader 去加载,BootstrapClassLoader 发现自己已经加载了 Object 类,直接返回给 Tomcat 的类加载器,这样 Tomcat 的类加载器就不会去加载 Web 应用下的 Object 类了,也就避免了覆盖 JRE 核心类的问题。
    4. 如果 ExtClassLoader 加载器加载失败,也就是说 JRE 核心类中没有这类,那么就在本地 Web 应用目录下查找并加载。
    5. 如果本地目录下没有这个类,说明不是 Web 应用自己定义的类,那么由系统类加载器去加载。这里请你注意,Web 应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。
    6. 如果上述加载过程全部失败,抛出 ClassNotFound 异常。

    总结

    Tomcat是如何打破双亲委派机制的呢?

    Tomcat是先去本地目录加载,为了避免本地目录覆盖掉JRE的核心类,如java.lang包等,先尝试用ExtClassLoader加载,这样即能打破双亲委派机制,有避免了覆盖掉核心类。

    为什么不是尝试用AppClassLoader加载呢?

    如果是尝试用AppClassLoader,这样不又变会双亲委派机制了嘛。

    展开全文
  • Tomcat破坏双亲委派机制带来的面试题 ①. Tomcat类加载机制 ①. 可以看到,在原来的 JVM 的类加载机制上面,Tomcat 新增了几个类加载器,包括 3 个基础类加载器和每个 Web 应用的类加载器。3个基础类加载器在 conf/...

    ①. Tomcat类加载机制

    • ①. 可以看到,在原来的 JVM 的类加载机制上面,Tomcat 新增了几个类加载器,包括 3 个基础类加载器和每个 Web 应用的类加载器。3个基础类加载器在 conf/catalina.properties 中进行配置:
    common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
    server.loader=
    shared.loader=
    
    • ②.Common:以应用类加载器为父类,是Tomcat顶层的公用类加载器,其路径由conf/catalina.pr operties中的common.loader指定,默认指向${catalina.home}/lib下的包

    • ③. Catalina:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不 可见,其路径由server.loader指定,默认为空,此时Tomcat使用Common类加载器加载应用服务器

    • ④. Shared:以Common类加载器为父类,是所有Web应用的父类加载器,其路径由shared.loader指定,默认为空,此时Tomcat使用Common类加载器作为Web应用的父加载器

    • ⑤. Web应用:以Shared类加载器为父类,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的jar包,该类加载器只对当前Web应用可见,对其他Web应用均不可见

    • ⑥. Tomcat8 和 Tomcat6比较大的区别是:
      Tomcat8可以通过配置 <Loader delegate="true"/>表示遵循双亲委派机制

    在这里插入图片描述

    ②. Tomcat执行顺序

    • ①. 使用bootstrap引导类加载器加载

    • ②. 使用system系统类加载器加载

    • ③. 使用应用类加载器在WEB-INF/classes中加载

    • ④. 使用应用类加载器在WEB-INF/lib中加载

    • ⑤. 使用common类加载器在CATALINA_HOME/lib中加载

    • ⑥.我们已经知道了tomcat为什么要这么设计,以及是如何设计的,那么,tomcat违背了java推荐的双亲委派模型了吗?答案是:违背了。我们前面说过:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。
      很显然,tomcat不是这样实现,tomcat为了实现隔离性,没有遵守这个约定,每个webappClassLoad er加载自己的目录下的class文件,不会传递给父类加载器。

    在这里插入图片描述

    ③. ClassLoader的创建

    • ①. 加载器类图:
      在这里插入图片描述

    • ②. 先从 BootStrap 的main方法看起:
      可以看到这里先判断了bootstrap是否为null,如果不为null直接把CatalinaClassLoader设置到了当前线程,如果为null下面是走到了init()方法

    public static void main(String args[]) {
        synchronized (daemonLock) {
            if (daemon == null) {
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try {
                    bootstrap.init();
                } catch (Throwable t) {
                    handleThrowable(t);
                    t.printStackTrace();
                    return;
                }
                daemon = bootstrap;
            } else {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to
                // prevent a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
            // 省略其余代码...
        }
    }
    
    • ③. 接着这里看到了会调用 initClassLoaders() 方法进行类加载器的初始化,初始化完成后,同样会设置 Catalina ClassLoader到当前线程
    public void init() throws Exception {
        // 初始化类加载器
        initClassLoaders();
        // 设置线程类加载器,将容器的加载器传入
        Thread.currentThread().setContextClassLoader(catalinaLoader);
        // 设置区安全类加载器
        SecurityClassLoad.securityClassLoad(catalinaLoader);
        // 省略其余代码...
    }
    
    • ④ .看到这里应该就清楚了,会创建三个ClassLoader:CommClassLoader,CatalinaClassLoader,SharedCla ssLoader,正好对应前面介绍的三个基础类加载器
    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }
    
    • ⑤. 接着进入createClassLoader() 查看代码:
      可以看到,这里加载的资源正好是我们刚才看到的配置文件conf/catalina.properties 中的 common.loader ,server.loader 和 shared.loader
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
    
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
        value = replace(value);
        List<Repository> repositories = new ArrayList<>();
        String[] repositoryPaths = getPaths(value);
        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }
            // Local repository
            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));
            }
        }
    
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
    

    ④. ClassLoader加载过程

    • ①. 接打开 ParallelWebappClassLoader ,至于为啥不是看 WebappClassLoader ,从名字上就知道 ParallelW ebappClassLoader 是一个并行的 WebappClassLoader
      然后看下 ParallelWebappClassLoader 的 loadclass 方法是在它的父类 WebappClassLoaderBase 中实现的
     1 public Class loadClass(String name, boolean resolve)
     2             throws ClassNotFoundException {
     3         Class clazz = null;
     4         // (0) 先从自己的缓存中查找,有则返回,无则继续
     5         clazz = findLoadedClass0(name);
     6         if (clazz != null) {
     7             if (resolve) resolveClass(clazz);            
     8             return (clazz);
     9         }
    10         // (0.1) 再从parent的缓存中查找
    11         clazz = findLoadedClass(name);
    12         if (clazz != null) {
    13             if (resolve) resolveClass(clazz);
    14             return (clazz);
    15         }
    16         // (0.2) 缓存中没有,则首先使用system类加载器来加载
    17         clazz = system.loadClass(name);
    18          if (clazz != null) {
    19              if (resolve) resolveClass(clazz);
    20              return (clazz);
    21          }
    22         //判断是否需要先让parent代理
    23         boolean delegateLoad = delegate || filter(name);
    24         // (1) 先让parent加载,通常delegateLoad == false,即这一步不会执行
    25 
    26         if (delegateLoad) {
    27             ClassLoader loader = parent;
    28             if (loader == null)
    29                 loader = system;
    30             clazz = loader.loadClass(name);
    31             if (clazz != null) {
    32                 if (resolve) resolveClass(clazz);
    33                 return (clazz);
    34             }
    35         }
    36         // (2) delegateLoad == false 或者 parent加载失败,调用自身的加载机制
    37         clazz = findClass(name);
    38         if (clazz != null) {
    39             if (resolve) resolveClass(clazz);
    40             return (clazz);
    41         }
    42         // (3) 自己加载失败,则请求parent代理加载
    43 
    44         if (!delegateLoad) {
    45             ClassLoader loader = parent;
    46             if (loader == null)
    47                 loader = system;
    48             clazz = loader.loadClass(name);
    49             if (clazz != null) {
    50                 return (clazz);
    51             }
    52         }
    53         throw new ClassNotFoundException(name);
    54     }
    
    • ②. 总结如上源码:
    1. 先从缓存中加载(自己的缓存和父类中的缓存)
    2. 如果没有,则从JVM的Bootstrap类加载器加载;
    3. 如果没有,则从当前类加载器加载(按照 WEB-INF/classes 、 WEB-INF/lib 的顺序)
    4. 如果没有,则从父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序是 AppClassLoader 、 Common 、 Shared

    ⑤. Tomcat破坏双亲委派机制带来的面试题

    • ①. 既然Tomcat不遵循双亲委派机制,那么如果我自己定义一个恶意的HashMap,会不会有风险呢?(阿里面试问题)
      显然不会有风险,如果有,Tomcat都运行这么多年了,那能不改进吗?
      tomcat不遵循双亲委派机制,只是自定义的classLoader顺序不同,但顶层还是相同的,还是要去顶层请求classloader

    • ②. 我们思考一下:Tomcat是个web容器,那么它要解决什么问题?

    1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是扯淡的。
    3. web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
    4. web容器要支持jsp的修改,我们知道,jsp文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用?所以,web容器需要支持jsp修改后不用重启。
    • ③. Tomcat如果使用默认的类加载机制行不行?
      答案是不行的。为什么?我们看:
      第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
      第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
      第三个问题和第一个问题一样。
      第四个问题,我们想我们要怎么实现jsp文件的热替换,jsp文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

    • ④. 如果tomcat的CommonClassLoader想加载WebAppClassLoader中的类,该怎么办?
      看了前面的关于破坏双亲委派模型的内容,我们心里有数了,我们可以使用线程上下文类加载器实现,使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。

    • ⑤. 为什么java文件放在Eclipse/IDEA中的src文件夹下会优先jar包中的class?
      tomcat类加载机制的理解,就不难明白。因为Eclipse/IDEA中的src文件夹中的文件java以及webContent中的JSP都会在tomcat启动时,被编译成class文件放在WEB-INF/class中。
      而Eclipse/IDEA外部引用的jar包,则相当于放在WEB-INF/lib中。
      因此肯定是java文件或者JSP文件编译出的class优先加载。

    • ⑥. 为什么JDBC需要打破双亲委派机制
      JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包,DriverManager类中要加载各个实现了Driver接口的类,然后进行管理,也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类,这就打破了双亲委派机制

    展开全文
  • tomcat 双亲委派源码分析
  • tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器...1. 不打破双亲委派一样可以保证不同web应用加载不同位置的内容,这是通过每个web.
  • Tomcat如何打破双亲委派机制实现隔离Web应用的?

    千次阅读 多人点赞 2021-07-29 01:29:04
    Tomcat通过自定义类加载器WebAppClassLoader打破双亲委托,即重写了JVM的类加载器ClassLoader的findClass方法和loadClass方法,这样做的目的是优先加载Web应用目录下的类。除此之外,你觉得Tomcat的类加载器还需要...
  • Tomcat 对双亲委派的破坏 首先,我们需要思考一个问题:Tomcat 为什么要打破双亲委派机制Tomcat 是个 web 容器, 那么它要解决什么问题呢: 一个 web 容器可能需要部署两个应用程序,不同的应用程序可能会依赖同...
  • 类加载机制是什么概论.java文件通过编译以后,读取到jvm的方法区中,然后类加载器的加载、验证(class特定的格式)、准备(分配内存)、解析(将字节码的符号引用改成直接引用)、初始化(对象的初始化)过程几种常见的类...
  • 首先我们说一下什么是双亲委派机制 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此...
  • 1、背景: ...安装JVM的双亲委派机制,可能存在互相覆盖的情况; 所以为了解决以上问题,tomcat提供WebAPP ClassLoader : 加载各自应用下的WEB-INF/class WEB-INF/lib ; 2)当Tomcat类 与WEB应...
  • JVM - 彻底理解打破双亲委派机制

    千次阅读 多人点赞 2020-06-12 00:11:41
    JVM-白话聊一聊JVM类加载和双亲委派机制源码解析 JVM - 自定义类加载器 何为打破双亲委派 举个例子 有个类 Artisan 我们希望通过自定义加载器 直接从某个路径下读取Artisan.class . 而不是说 通过自定义加载器 委托...
  • 如何打破双亲委派机制

    千次阅读 2021-02-21 22:57:28
    第一次知道何为打破双亲委派机制是通过阅读周志明的《深入理解Java虚拟机》,我们知道双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在...
  • Tomcat中会部署多个应用,每个应用可能会出现相同的类名,或者引用相同的jar包但版本不同,若使用双亲委派,一个类只会加载一次,项目就互相之间有冲突,若打破双亲委派,可以实现各自的应用加载自己应用的类,互不...
  • 应用类加载器的创建双亲委派机制双亲委派机制源码加载递归调用图设计双亲委派的目的自定义类加载器打破双亲委派机制Tomcat打破双亲委派机制模拟实现Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互...
  • 中对应的类加载器就是AppClassLoader,满足双亲委派机制 4、打破双亲委派机制 例如在Tomact中部署多个项目,每个项目使用的相同但不用版本的组件 自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器...
  • Tomcat打破双亲委派

    2021-05-16 23:00:52
    复习复习JVM类加载机制,再谈谈 Tomcat 的类加载器如何打破 Java 的双亲委托机制。 JVM 的类加载器 Java 的类加载,就是把字节码格式“.class”文件加载到 JVM 的方法区,并在 JVM 的堆区建立一个java.lang.Class...
  • java默认的类加载器有三个,分别是引导类加载器、扩展类加载器以及应用类加载器。... 那么何为双亲委派机制呢,双亲委派机制就是在进行类加载的时候,如果当前类加载器存在父加载器,那么首相从父类加载器中获取...
  • 文章目录打破双亲委派的原因Demo 打破双亲委派的原因 双亲委派的机制,是AppClassLoader先委托给ExtClassLoader类加载器进行加载,ExtClassLoader加载不了,则交由BootstrapClassLoader进行加载操作; 如果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,805
精华内容 722
关键字:

tomcat打破双亲委派机制