tomcat类加载体系

类加载,再来一发。

研究完java提供的类加载机制,再来看看tomcat开出了那些花。

最近开始读tomcat的源码,主线路当然是类加载机制,在这个过程中有豁然开朗的感觉。这一篇主要是自己的体会,而不是从头到尾的详细解读。很显然,是因为我懒。有多懒呢,懒到把女朋友都弄丢了,哎。

言归正传,从tomcat的启动类Bootstrap开始叙述。

1、加载Bootstrap的类加载器是哪个呢?APPClassLoader,因为tomcat还是要依赖Java的基础,包括类加载器和双亲委派模型。

2、Bootstrap类启动的时候,会初始化类加载器。

    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;

    // -------------------------------------------------------- Private Methods

    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);
        }
    }

这里有3个类加载器,catalinaLoader 和 sharedLoader 以 commonLoader 为父类加载器,那commonLoader的父类加载器呢???一路跟代码,发现最终是AppClassLoader,具体实现在Java.lang.ClassLoader类。

common加载的类:tomcat和web应用都可以访问;

catalina加载的类:tomcat内部实现用到的类;

shared加载的类:所有web应用可以共用。

与每个web应用单独对应的类加载器是WebappClassLoader,后面会具体分析。

3、common、catalina以及shared三个类加载器都是URLClassLoader

    if (parent == null)
         return new URLClassLoader(array);
    else
         return new URLClassLoader(array, parent);

这是tomcat7的代码,6包括之前的版本还在用StandardClassLoader

4、类加载器生成后,Thread.currentThread().setContextClassLoader(catalinaLoader); 将当前线程的上下文类加载器设置为catalinaLoader,在后面的逻辑里面会经常用到。

5、再之后的一段代码需要好好分析一下

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

这里通过catalinaLoader去加载了Catalina类,生成了一个实例,并且通过反射调用了这个实例的setParentClassLoader方法,将父类加载器设置为sharedLoader,后面也会经常用到。我刚看到这段代码时,有一个巨大的疑问,为什么要用反射呢?

说的这里,我想再延伸一下。使用不同的类加载器还有一个好处:类之间的相互隔离。再深入一点,初始加载器和定义加载器。比如:有A、B、C 3个类加载器,根据双亲委派,通过 A --> B --> C 这条路径去加载了一个类 X, 那么A、B、C都是X的初始加载器,只有C是X的定义加载器。有一个原则:X类型在A、B、C 的命名空间中共享,在别的类加载器的命名空间中不能访问,也就是隔离。这里要注意,A、B独自加载的类型,对C不共享,也就是说子共享父,反过来不行。

所以,现在能解释两个事情:

a)、我们平常的代码里处处是jdk的类,它们的类加载器是启动类加载器,为什么。因为我们的代码是在classpath里面,类加载器是AppClassLoader,可以共享jdk的类。

b)、我们这里必须用反射!!!路演一下,启动类Bootstrap的类加载器是AppClassLoader,然后Bootstrap类里面用catalinaLoader去加载Catalina类。catalina --> common --> AppClassLoader,所以顺序很重要,反过来不行。既然不能共享,但是必须要访问,那怎么办呢,还好有反射。也就是说,反射突破了类加载的隔离机制。

6、最后来解刨WebappClassLoader

先看它重写的loadClass方法,实现在父类WebappClassLoaderBase里面

public Class<?> loadClass(String name, boolean resolve) throws         ClassNotFoundException {

        synchronized (getClassLoadingLockInternal(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped classloader
            if (!started) {
                try {
                    throw new IllegalStateException();
                } catch (IllegalStateException e) {
                    log.info(sm.getString("webappClassLoader.stopped", name), e);
                }
            }

            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.1) Check our previously loaded class cache
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding J2SE classes
            try {
                clazz = j2seClassLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf(‘.‘);
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        if (name.endsWith("BeanInfo")) {
                            // BZ 57906: suppress logging for calls from
                            // java.beans.Introspector.findExplicitBeanInfo()
                            log.debug(error, se);
                        } else {
                            log.info(error, se);
                        }
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            boolean delegateLoad = delegate || filter(name);

            // (1) Delegate to our parent if requested
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (2) Search local repositories
            if (log.isDebugEnabled())
                log.debug("  Searching local repositories");
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally
            if (!delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader at end: " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }

梳理一下流程,其实英文的注释已经很明白了。

(1)首先从Tomcat自己持有的缓存中去查找

(2)从JVM的缓存中找

(3)先用j2seClassLoader去加载,就是怕你把jdk的jar包放到了WEB-INF下面

        ClassLoader j = String.class.getClassLoader();
        if (j == null) {
            j = getSystemClassLoader();
            while (j.getParent() != null) {
                j = j.getParent();
            }
        }
        this.j2seClassLoader = j;    

从这段代码中,你应该知道j2seClassLoader到底是哪个类加载器了。只是我不明白为什么要用这么复杂的方式得到,getSystemClassLoader().getParent() 不行吗,希望明白的读者告知。

(4)如果设置了代理,就代理给父类加载器去加载,父亲是谁呢?后面再说。

(5)终于轮到自己去加载了

(6)如果还是不好使,无条件的交给父亲去搞定。

现在看看,这个WebappClassLoader是怎么生成的:

启动StandardContext的时候会创建WebappLoader,WebappLoader持有WebappClassLoderBase,WebappLoader里面初生成它的代码

    private String loaderClass =

"org.apache.catalina.loader.WebappClassLoader";


  classLoader = createClassLoader();

    private WebappClassLoaderBase createClassLoader()
        throws Exception {

        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;

        if (parentClassLoader == null) {
            parentClassLoader = container.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);

        return classLoader;

    }

这里通过反射调用构造方法,生成一个WebappClassLoader,并强转成父类。

其中container.getParentClassLoader() 得到的类加载器,就是sharedLoader,去看Tomcat的源码。

至于这里为什么也要用反射,不明白,请读者指教。

时间: 2024-10-08 21:13:43

tomcat类加载体系的相关文章

Tomcat源码分析——类加载体系

前言 Tomcat遵循J2EE规范,实现了Web容器.很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解.此外,Tomcat还根据Java虚拟机规范实现了经典的双亲委派模式的类加载体系.本文基于Tomcat7.0的Java源码,对其类加载体系进行分析. 概述 本节简单介绍Java虚拟机规范中提到的主要类加载器: Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.p

Tomcat类加载

一.为什么会有类加载 1.在类加载阶段,虚拟机需要完成以下3件事情 1)通过一个全限类定名来获取此类的二进制字节流 2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 3)在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据结构访问入口 2.虚拟机将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示: 2)链接: 验证:确保被加载类的正确性:(验证不过会抛出 java.l

java类加载器-Tomcat类加载器

在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式.接下来就以tomcat6为例看看tomat是如何使用自定制类加载器的.(本介绍是基于tomcat6.0.41,不同版本可能存在差异!) 网上所描述的tomcat类加载器 在网上搜一下“tomcat类加载器”会发现有大量的文章,在此我偷个懒,^_^把网上对tomcat类加载器的描述重说一下吧. CommonClassLoader:加载的类目录通过{tomcat}/conf/catalina.properties中的co

Tomcat类加载机制

说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷. 之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试.后来同事指导,说是直接把java类复制到src下就可以了.很纳闷....为什么会优先加载src下的java文件(编译出的class),而不是jar包中的class呢? 现在了解tomcat的类加载机制,原来一切是这么的简单. 类加载 在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加

图解Tomcat类加载机制

说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷. 之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试.后来同事指导,说是直接把java类复制到src下就可以了.很纳闷....为什么会优先加载src下的java文件(编译出的class),而不是jar包中的class呢? 现在了解tomcat的类加载机制,原来一切是这么的简单. 类加载 在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加

Tomcat类加载器

1JVM类加载机制 JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构.其中引导类.扩展类.系统类三个加载器是JVM内置的. 它们的作用分别是: 1)引导类加载器:使用native代码实现,在rt.jar等包中搜索运行JVM所需的类,例如java.lang等包下的类. 2)扩展类加载器:负责载入标准扩展目录中的类,例如Sun的JVM的扩展目录是/jdk/jre/lib/ext. 3)系统类加载器:默认的类加载器,搜索环境变量CLASSPATH中指明的路径. 2双亲委

图解JVM和Tomcat类加载机制

说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷. 之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试.后来同事指导,说是直接把java类复制到src下就可以了.很纳闷....为什么会优先加载src下的java文件(编译出的class),而不是jar包中的class呢? 现在了解tomcat的类加载机制,原来一切是这么的简单. 类加载 在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加

《转载》图解Tomcat类加载机制

本文转载自http://www.cnblogs.com/xing901022/p/4574961.html 说到本篇的tomcat类加载机制,不得不说翻译学习tomcat的初衷. 之前实习的时候学习javaMelody的源码,但是它是一个Maven的项目,与我们自己的web项目整合后无法直接断点调试.后来同事指导,说是直接把java类复制到src下就可以了.很纳闷....为什么会优先加载src下的java文件(编译出的class),而不是jar包中的class呢? 现在了解tomcat的类加载机

深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现

打破双亲委派模型 JNDI JNDI 的理解 JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一个,不少专家觉得,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识. 那么,JNDI究竟起什么作用?//带着问题看文章是最有效的 要了解JNDI的作用,我们能够从“假设不用JNDI我们如何做?用了JNDI后我们又将如何做?”这个问题来探讨. 没有JNDI的做法: 程序猿开发时,