zoukankan      html  css  js  c++  java
  • Tomcat源码分析(八)载入器

    本系列转载自 http://blog.csdn.net/haitao111313/article/category/1179996  

    在讲Tomcat的载入器之前,先要了解一下java的类加载机制,这里就不具体说了,仅仅写一点我认为比较重要的东西:

        1:一般实现自己的类加载器是重写ClassLoader的findClass方法,然后在这个方法里面读取class文件为byte[]数组,传入defineClass方法,defineClass方法返回我们加载的类。这样便实现了我们自己的简单的类加载器。下面是一个简单的自定义类加载器的findClass方法:

    1. protected Class<?> findClass(String name) throws ClassNotFoundException {   
    2.         byte[] classData = getClassData(name); //getClassData方法是通过class文件的名字得到一个字节数组  
    3.         if (classData == null) {   
    4.             throw new ClassNotFoundException();   
    5.         }   
    6.         else {   
    7.             return defineClass(name, classData, 0, classData.length); //加载进jvm中。  
    8.         }   
    9.     }   


    问题是: 可不可以重写ClassLoader的loadClass方法来实现自己的类加载器? 答案是可以,Tomcat就是用的这种方法。jdk建议我们实现自己的类加载器的时候是重写findClass方法,不建议重写loadclass方法,因为ClassLoader的loadclass方法保证了类加载的父亲委托机制,如果重写了这个方法,就意味着需要实现自己在重写的loadclass方法实现父亲委托机制,下面看看ClassLoader的loadclass方法,看怎么实现父亲委托机制的:

    1. protected synchronized Class<?> loadClass(String name, boolean resolve)  
    2. throws ClassNotFoundException  
    3.    {  
    4. // First, check if the class has already been loaded  
    5. Class c = findLoadedClass(name);//检查该类是否已经被加载了  
    6. if (c == null) {  
    7.     try {  
    8.     if (parent != null) {//如果父加载器不为空就用父加载器加载该类。  
    9.         c = parent.loadClass(name, false);  
    10.     } else {  
    11.         c = findBootstrapClass0(name);  
    12.     }  
    13.     } catch (ClassNotFoundException e) {  
    14.         // If still not found, then invoke findClass in order  
    15.         // to find the class.  
    16.         c = findClass(name);//在自己实现的类加载器中重写这个findClass方法。  
    17.     }  
    18. }  
    19. if (resolve) {  
    20.     resolveClass(c);  
    21. }  
    22. return c;  
    23.    }  
    这样就保证了父亲委托机制。当所有父类都加载不了,才会调用findClass方法,即调用到我们自己的类加载器的findClass方法。以此实现类加载。

    2:在我们自己实现的类加载器中,defineClass方法才是真正的把类加载进jvm,defineClass是从ClassLoader继承而来,把一个表示类的字节数组加载进jvm转换为一个类。

    3:我们自己实现的类加载器跟系统的类加载器没有本质的区别,最大的区别就是加载的路径不同,系统类加载器会加载环境变量CLASSPATH中指明的路径和jvr文件,我们自己的类加载器可以定义自己的需要加载的类文件路径.同样的一个class文件,用系统类加载器和自定义加载器加载进jvm后类的结构是没有区别的,只是他们访问的权限不一样,生成的对象因为加载器不同也会不一样.当然我们自己的类加载器可以有更大的灵活性,比如把一个class文件(其实就是二进制文件)加密后(简单的加密就把0和1互换),系统类加载器就不能加载,需要由我们自己定义解密类的加载器才能加载该class文件.

    现在来初步的看看Tomcat的类加载器,为什么Tomcat要有自己的类加载器.这么说吧,假如没有自己的类加载器,我们知道,在一个Tomcat中是可以部署很多应用的,如果所有的类都由系统类加载器来加载,那么部署在Tomcat上的A应用就可以访问B应用的类,这样A应用与B应用之间就没有安全性可言了。还有一个原因是因为Tomcat需要实现类的自动重载,所以也需要实现自己的类加载器。Tomcat的载入器是实现了Loader接口的WebappLoader类,也是Tomcat的一个组件,实现Lifecycle接口以便统一管理,启动时调用start方法,在start方法主要做了以下的事情:

    1:创建一个真正的类加载器以及设置它加载的路径,调用createClassLoader方法

    2:启动一个线程来支持自动重载,调用threadStart方法

    看createClassLoader方法:

    1. private WebappClassLoader createClassLoader()  
    2.        throws Exception {  
    3.   
    4.        Class clazz = Class.forName(loaderClass);  
    5.        WebappClassLoader classLoader = null;  
    6.   
    7.        if (parentClassLoader == null) {  
    8.            // Will cause a ClassCast is the class does not extend WCL, but  
    9.            // this is on purpose (the exception will be caught and rethrown)  
    10.            classLoader = (WebappClassLoader) clazz.newInstance();  
    11.        } else {  
    12.            Class[] argTypes = { ClassLoader.class };  
    13.            Object[] args = { parentClassLoader };  
    14.            Constructor constr = clazz.getConstructor(argTypes);  
    15.            classLoader = (WebappClassLoader) constr.newInstance(args);  
    16.        }  
    17.   
    18.        return classLoader;  
    19.   
    20.    }  

    在createClassLoader方法内,实例化了一个真正的类加载器WebappClassLoader,代码很简单。WebappClassLoader是Tomcat的类加载器,它继承了URLClassLoader(这个类是ClassLoader的孙子类)类。重写了findClass和loadClass方法。Tomcat的findClass方法并没有加载相关的类,只是从已经加载的类中查找这个类有没有被加载,具体的加载是在重写的loadClass方法中实现,从上面的对java的讨论可知,重写了loadClass方法就意味着失去了类加载器的父亲委托机制,需要自己来实现父亲委托机制。Tomcat正是这么做的,下面看WebappClassLoader的loadClass方法:

    1. public Class loadClass(String name, boolean resolve)  
    2.         throws ClassNotFoundException {  
    3.         if (debug >= 2)  
    4.             log("loadClass(" + name + ", " + resolve + ")");  
    5.         Class clazz = null;  
    6.   
    7.         // Don't load classes if class loader is stopped  
    8.         if (!started) {  
    9.             log("Lifecycle error : CL stopped");  
    10.             throw new ClassNotFoundException(name);  
    11.         }  
    12.   
    13.         // (0) Check our previously loaded local class cache  
    14.         //查找本地缓存是否已经加载了该类  
    15.         clazz = findLoadedClass0(name);  
    16.         if (clazz != null) {  
    17.             if (debug >= 3)  
    18.                 log("  Returning class from cache");  
    19.             if (resolve)  
    20.                 resolveClass(clazz);  
    21.             return (clazz);  
    22.         }  
    23.   
    24.         // (0.1) Check our previously loaded class cache  
    25.         
    26.         clazz = findLoadedClass(name);  
    27.         if (clazz != null) {  
    28.             if (debug >= 3)  
    29.                 log("  Returning class from cache");  
    30.             if (resolve)  
    31.                 resolveClass(clazz);  
    32.             return (clazz);  
    33.         }  
    34.   
    35.         // (0.2) Try loading the class with the system class loader, to prevent  
    36.         //       the webapp from overriding J2SE classes  
    37.         try {  
    38.             clazz = system.loadClass(name);// (0.2) 先检查系统ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的类定义不能覆盖JVM 底层能够查找到的定义(譬如不能通过定义java.lang.Integer替代底层的实现  
    39.             if (clazz != null) {  
    40.                 if (resolve)  
    41.                     resolveClass(clazz);  
    42.                 return (clazz);  
    43.             }  
    44.         } catch (ClassNotFoundException e) {  
    45.             // Ignore  
    46.         }  
    47.   
    48.        ...............................................  
    49. //这是一个很奇怪的定义,JVM的ClassLoader建议先由parent去load,load不到自己再去load(见如上 ClassLoader部分),而Servelet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的 delegate定义),默认使用Servlet规范的建议,即delegate=false  
    50.         boolean delegateLoad = delegate || filter(name);  
    51. // (1) 先由parent去尝试加载,此处的parent是SharedClassLoader,见如上说明,如上说明,除非设置了delegate,否则这里不执行  
    52.         // (1) Delegate to our parent if requested  
    53.         if (delegateLoad) {  
    54.             if (debug >= 3)  
    55.                 log("  Delegating to parent classloader");  
    56.             ClassLoader loader = parent;  
    57.             if (loader == null)//此处parent是否为空取决于context 的privileged属性配置,默认privileged=true,即parent为SharedClassLoader  
    58.                 loader = system;  
    59.             try {  
    60.                 clazz = loader.loadClass(name);  
    61.                 if (clazz != null) {  
    62.                     if (debug >= 3)  
    63.                         log("  Loading class from parent");  
    64.                     if (resolve)  
    65.                         resolveClass(clazz);  
    66.                     return (clazz);  
    67.                 }  
    68.             } catch (ClassNotFoundException e) {  
    69.                 ;  
    70.             }  
    71.         }  
    72.   
    73.        // 到WEB-INF/lib和WEB-INF/classes目录去搜索,细节部分可以再看一下findClass,会发现默认是先搜索WEB-INF/classes后搜索WEB-INF/lib  
    74.         if (debug >= 3)  
    75.             log("  Searching local repositories");  
    76.         try {  
    77.             clazz = findClass(name);  
    78.             if (clazz != null) {  
    79.                 if (debug >= 3)  
    80.                     log("  Loading class from local repository");  
    81.                 if (resolve)  
    82.                     resolveClass(clazz);  
    83.                 return (clazz);  
    84.             }  
    85.         } catch (ClassNotFoundException e) {  
    86.             ;  
    87.         }  
    88.   
    89.         // (3) Delegate to parent unconditionally  
    90.         if (!delegateLoad) {  
    91.             if (debug >= 3)  
    92.                 log("  Delegating to parent classloader");  
    93.             ClassLoader loader = parent;  
    94.             if (loader == null)  
    95.                 loader = system;  
    96.             try {  
    97.                 clazz = loader.loadClass(name);  
    98.                 if (clazz != null) {  
    99.                     if (debug >= 3)  
    100.                         log("  Loading class from parent");  
    101.                     if (resolve)  
    102.                         resolveClass(clazz);  
    103.                     return (clazz);  
    104.                 }  
    105.             } catch (ClassNotFoundException e) {  
    106.                 ;  
    107.             }  
    108.         }  
    109.   
    110.         // This class was not found  
    111.         throw new ClassNotFoundException(name);  
    112.   
    113.     }  

    上面的代码很长,但实现了Tomcat自己的类加载机制,具体的加载规则是:

    1:因为所有已经载入的类都会缓存起来,所以先检查本地缓存

    2:如本地缓存没有,则检查上一级缓存,即调用ClassLoader类的findLoadedClass()方法;

    3:若两个缓存都没有,则使用系统的类进行加载,防止Web应用程序中的类覆盖J2EE的类

    4:若打开标志位delegate(表示是否代理给父加载器加载),或者待载入的类是属于包触发器的包名,则调用父类载入器来加载,如果父类载入器是null,则使用系统类载入器

    5:从当前仓库中载入相关类

    6:若当前仓库中没有相关类,且标志位delegate为false,则调用父类载入器来加载,如果父类载入器是null,则使用系统类载入器(4跟6只能执行一个步骤的)

    这里有有一点不明白的是在第三步使用系统类加载若失败后,在第四步和第六步就没必要但父类载入器为null的时候再用系统类载入器来载入了???有谁明白的麻烦留个言,不胜感激~

    参考了一下http://ayufox.iteye.com/blog/631190这篇博文.

  • 相关阅读:
    CSS盒子模型
    getContextPath、getServletPath、getRequestURI、request.getRealPath的区别
    MYSQL中的CASE WHEN END AS
    单点登录的精华总结
    git&github
    June 21st 2017 Week 25th Wednesday
    June 20th 2017 Week 25th Tuesday
    June 19th 2017 Week 25th Monday
    June 18th 2017 Week 25th Sunday
    June 17th 2017 Week 24th Saturday
  • 原文地址:https://www.cnblogs.com/chenying99/p/2798447.html
Copyright © 2011-2022 走看看