多多吃红薯,好好学java(二)-->类加载器的体系及Tomcat的类加载器

来源:互联网 发布:车钥匙淘宝图片 编辑:程序博客网 时间:2024/04/28 13:58

修正上次笔记的一个错误,类的装载,连接,初始化在JVM规范中是一个连续的过程(但并不需要完成一项再完成另一项),但类装载的时机JVM规范没有明确中指出,不同虚拟机会有不同的实现(《深入理解Java虚拟机》和《深入JVM》均有指出),也许会在第一次访问静态域或者静态方法时装载,也许会预先装载所有类。

众所周知,Java的类加载器的机制是父委派制(这里说的是传统的类加载器模型,osgi的实现了依靠的是更灵活的类加载器体系),比如我们的程序中某处用到了自定义的的某类,当到了装载时机时,此时system classloader会先交给父加载器extension classloader,然后再由extension classloader交给bootstarp classloader来装载此类,显然bootstarp classloader无法装载此类,再反交回extension classloader,extension classloader当然也无法加载,再反交回system classloader,如果还不能加载(此类可能来源于网络),便抛出我们熟悉的java.lang.ClassNotFoundException,从这个异常的名字我们其实就能看出类装载器的实际用途-->根据类的前线定名按照一定的方式(从文件系统里,从网络上等等)找到类文件(返回类文件的二进制形式),从这个异常名里我们看不出的是,当类装载后会根据类文件生成一个Class类型的对象,这个Class对象会与反射息息相关,需要在下篇笔记里记录。

题外话,既然我们能按一定的方式拿到类文件的byte [],那么我们就可以利用字节码生成技术对原来的类文件在加载阶段进行一定的增强处理,AOP的织入有3个时机(编译时,加载时,运行时),

其中加载时织入,便是通过自定义的classloader运用字节码生成技术对原有的类文件(需要被代理的对象)进行响应切面字节码的织入来生成增强后的类文件(代理)实现的


可以明显看出,一个类的装载过程一定会走这个么来回,也就是不管如何,其一定会经过3种类加载器。

这么做是有道理了,是出于安全考虑的,java的核心类被信任的是可以绕过安全沙箱的,可以考虑一下场景,我们在应用程序中用我们自定义的一个java.io.InputStream类,我们故意让它和java核心类的名称相同,它是一个很邪恶的类,它会肆意访问硬盘,在父委派体系下,它会经过逐层传递到bootstarp classloader中,bootstarp classloader会搜索java核心库中的java.io.InputStream然后加载它,也就是说我们自定义的java.io.InputStream的邪恶类不会被JVM错误的认为是核心类而绕过安全管理器,它不会被加载..


tomcat这只很帅气的猫虽然小有创新,大体上却依然恪守陈规,它的类加载器体系依然是传统的父委派体系,但其自定义了classloader(WebappClassLoader).tomcat6,7版本架构方式和4,5发生了巨变,我不知道类加载体系有没有变化。

以下以<<how works tomcat>>此书为学习资料(此书的背景是tomcat4,5),进行归纳总结理解。

tomcat有一个WebappLoader的类,它用来加载整个Web应用程序而不仅仅只是类加载器,它通过组合一个WebappClassLoader类,把加载类的任务委派给WebappClassLoader。

这里先关注WebappClassLoader,它继承自URLClassLoader,所以其可以加载jar包。

因为tomcat这里对类加载的逻辑进行了更细致的处理,所以不仅仅重写了findClass()方法(一般来说这样足以)并且重写了loadClass(name),这里所说的更精细的处理是在loadClass()方法中在调用finaClass()方法之前,WebappClassLoader先调用一个findLoadedClass0(name)的方法,WebappClassLoader对已经载入的类做了缓存来进行优化,findLoadedClass0(name)方法就是判断缓存里是否已经有加载过的此类,有就直接返回此Class对象,第二步是调用间接父类ClassLoader的findLoadedClass()来查找缓存里是否有此类,有返回Class对象,如果都没有,就用系统类加载器加载,它还有一个是否使用使用父委派机制的标识。

boolean delegateLoad = delegate || filter(name);

可以看出tomcat可以对特定的类名进行过滤,以让其不被委派给父加载器加载.

现在可以看一下,findClass方法了,看看tomcat到底是如何找到类文件

public Class findClass(String name) throws ClassNotFoundException {        Class clazz = null;        clazz = findClassInternal(name);        if ((clazz == null) && hasExternalRepositories) {            synchronized (this) {                //让父类RULClassLoader去查找类
                clazz = super.findClass(name);            }        }        if (clazz == null) {            throw new ClassNotFoundException(name);        }        return (clazz);    }

注意这行clazz = findClassInternal(name);

protected Class findClassInternal(String name)        throws ClassNotFoundException {        //对类的权限定名进行验证
        if (!validate(name))            throw new ClassNotFoundException(name);        //根据类名查找资源        String tempPath = name.replace('.', '/');        String classPath = tempPath + ".class";        ResourceEntry entry = null;        //tomcat会把类文件封装成一个ResourceEntry类
        entry = findResourceInternal(name, classPath);        if (entry == null)            throw new ClassNotFoundException(name);        //如果以前已经加载成功过这个类,直接返回        Class clazz = entry.loadedClass;        if (clazz != null)            return clazz;        synchronized (this) {            if (entry.binaryContent == null && entry.loadedClass == null)                throw new ClassNotFoundException(name);            String packageName = null;            int pos = name.lastIndexOf('.');            if (pos != -1)                packageName = name.substring(0, pos);             Package pkg = null;             if (packageName != null) {                pkg = getPackage(packageName);                if (pkg == null) {                    pkg = getPackage(packageName);                }            }             if (securityManager != null) {                ........            }            if (entry.loadedClass == null) {                try {                    //根据类文件的byte[]生成Class对象
                    clazz = defineClass(name, entry.binaryContent, 0,                            entry.binaryContent.length,                             new CodeSource(entry.codeBase, entry.certificates));                } catch (UnsupportedClassVersionError ucve) {                    throw new UnsupportedClassVersionError(                            ucve.getLocalizedMessage() + " " +                            sm.getString("webappClassLoader.wrongVersion",                                    name));                }                entry.loadedClass = clazz;                entry.binaryContent = null;                entry.source = null;                entry.codeBase = null;                entry.manifest = null;                entry.certificates = null;            } else {                clazz = entry.loadedClass;            }        }         return clazz;    }