浅读Tomcat源码(五)---classLoader

来源:互联网 发布:阿里云邮箱账号找回 编辑:程序博客网 时间:2024/06/07 10:51
本文其实是个伪标题,相比起Tomcat源码,本文的主要内容在于解读java的classLoader机制


对JVM有一点了解的人,应该都不会陌生java的classLoader机制了,这篇文章就着我自己学习中的一些不太理解的地方做一些分析。




一.作用

ClassLoader的作用是一个我一直比较苦恼的问题,因为似乎他的用法往往和Class.forName有一点点类似,都是传入了类的全称并生成了Class对象,我在网上搜索了一圈二者的区别,查到的回答来源于一篇博客,博客中提出二者的区别在于是否加载类中的静态区域。对于这个回答,我个人感觉非常的不精确。诚然是否加载静态区域或许的确是二者的一个差别,但是说二者的区别在于此就如同说自行车和汽车的区别在于轮胎的多少一样。


我的理解是,二者分明是两种不同的机制,Class.forName的过程中或许用到了ClassLoader的机制,其本质在于加载已经在ClassPath下的某个类。而ClassLoader本质却是将某个路径下的class文件加载进入虚拟机。可能这样表述有点不太清晰,来看个例子:

package com.example.test1;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class FileSystemClassLoader extends ClassLoader {     private String rootDir;     public FileSystemClassLoader(String rootDir) {         this.rootDir = rootDir;     }     protected Class<?> findClass(String name) throws ClassNotFoundException {         byte[] classData = getClassData(name);         if (classData == null) {             throw new ClassNotFoundException();         }         else {             return defineClass(name, classData, 0, classData.length);         }     }     private byte[] getClassData(String className) {         String path = classNameToPath(className);         try {             @SuppressWarnings("resource")InputStream ins = new FileInputStream(path);             ByteArrayOutputStream baos = new ByteArrayOutputStream();             int bufferSize = 4096;             byte[] buffer = new byte[bufferSize];             int bytesNumRead = 0;             while ((bytesNumRead = ins.read(buffer)) != -1) {                 baos.write(buffer, 0, bytesNumRead);             }             return baos.toByteArray();         } catch (IOException e) {             e.printStackTrace();         }         return null;     }     private String classNameToPath(String className) {         return rootDir + File.separatorChar                 + className.replace('.', File.separatorChar) + ".class";     }  }
这个类完成了对传入的rootDir下的class文件作出解读的过程,来看下调用:

public static void main(String[] args) {String rootDir = Thread.currentThread().getContextClassLoader().getResource("").getPath();System.out.println(rootDir);FileSystemClassLoader classLoader = new FileSystemClassLoader(rootDir);try {Object obj = classLoader.findClass("com.example.test1.InputBuffer").newInstance();System.out.println(obj.getClass());System.out.println(obj instanceof com.example.test1.InputBuffer);Object obj2 = Class.forName("com.example.test1.InputBuffer").newInstance();System.out.println(obj2.getClass());System.out.println(obj2 instanceof com.example.test1.InputBuffer);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
运行结果:
/E:/eclipse/workspace3/Test123/build/classes/class com.example.test1.InputBufferfalseclass com.example.test1.InputBuffertrue
我们可以看到两点:

1)用ClassLoader加载出来的对象,虽然其属性依旧是这个类,但是用instanceof去判断却是完全不同的两个类了,其原因在于用instanceof判断的时候,一个是自己的classLoader架载的类,一个是JDK自带的classLoader加载的,是具有一样名字的两个类

2)这里我们恰巧吧rootDir设置成了classPath,但是假如设置成为别的,我们可以利用classLoader将任意目录变为classPath,个人觉得eclipse之类的ide就是这么做的。

从这两点就可以看出classLoader和class.forName的区别了,一个是正儿八经的加载class文件,一个则是将已经分配了具体用于加载它的classLoader的类(即classPath下的类)获取到,完全就是两码事情。


二.双亲委派

上面那个例子我们看到,我们用了自己的类加载器加载出来的com.example.test1.InputBuffer类,和JDK自带的却不同,如果这样的情况大规模存在,java的类机制将会非常混乱,所以JDK有一种规范化的类加载结构,双亲委派,在我们刚刚的类中加入一段代码,修改后如下:

    protected Class<?> findClass(String name) throws ClassNotFoundException {     /**     * 新加入,先判断父加载器是否能加载     */    Class<?> c = getParent().loadClass(name);        if (c != null) {        return c;        }    byte[] classData = getClassData(name);         if (classData == null) {             throw new ClassNotFoundException();         }         else {             return defineClass(name, classData, 0, classData.length);         }     } 
然后重新运行上面的测试,结果如下:

/E:/eclipse/workspace3/Test123/build/classes/class com.example.test1.InputBuffertrueclass com.example.test1.InputBuffertrue
可以看到上面的判断中那个false变为了true,双亲委派中先委托父加载器去加载,如果父加载器加载不到再自己加载,这样子就可以不重复加载了,而且这种加载机制很好地处理了代码间的相互隔离,一会我们看tomcat中的类加载机制就可以知道了。在双亲委派模型下,JDK的类加载器的结构如下:



这个具体不解释了,图片也是盗来的,可以看原文:

http://blog.csdn.net/xyang81/article/details/7292380


三.Tomcat的类加载机制

由于对自己讲述的不太有信心,而且也懒得讲的过于详细,推荐读者先去看下我看的原文:

http://blog.csdn.net/beliefer/article/details/50995516


好,我们继续,先来看下Tomcat的结构图:



这个结构其实很有意思,Tomcat总共有四个自定义类加载器,common用来加载共用的,catalina加载Tomcat源码的,share加载webapp共用的,webapp用来加载每个webapp自己的,其实看这个双亲委派的关系图就明白了其中的原理,事实上对于webapp来说,他所需要的类全部都是webappClassLoader加载的,但是webappClassLoader会先让父加载器shaerdClassLoader找,以此类推,所以就实现了webapp可以用share文件夹下的所有jar包,tomcat和webapp都可以用common文件夹下的jar包。


虽然在tomcat5以后就没有common、share、catalina三个lib包分类,而是全部归在了lib文件夹下,但是对于这个结构的理解可以帮助我们更好理解双亲委派模型。




原创粉丝点击