Tomcat源码分析(八)--载入器

系统 1588 0
本系列转载自 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 这篇博文.

Tomcat源码分析(八)--载入器


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论