zoukankan      html  css  js  c++  java
  • 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,所以顺序很重要,反过来不行。既然不能共享,但是必须要访问,那怎么办呢,还好有反射。

    其实在Tomcat的源码里面,Bootstrap类和Catalina类是在同一个package里。那,问题又来了。class文件既然在同一个路径下,为什么一个用AppClassLoader来加载,而另一个却用CatalinaLoader呢???

    应该是出于安全的考虑,不信就去看看Bootstrap类的注释(重点是最后一句。翻译,我就不献丑了):

     * Bootstrap loader for Catalina.  This application constructs a class loader
     * for use in loading the Catalina internal classes (by accumulating all of the
     * JAR files found in the "server" directory under "catalina.home"), and
     * starts the regular execution of the container.  The purpose of this
     * roundabout approach is to keep the Catalina internal classes (and any
     * other classes they depend on, such as an XML parser) out of the system
     * class path and therefore not visible to application level classes.
    View Code

      

    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的源码。

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

  • 相关阅读:
    面向对象思想
    jQuery随笔
    总结关于linux操作
    转.linux上安装python
    sql server 基本语句
    linux 常见指令
    loadrunner 录制时不自动弹出网页
    Linux 安装MySQL
    linux关于安装
    loadrunner 性能测试
  • 原文地址:https://www.cnblogs.com/cz123/p/6885363.html
Copyright © 2011-2022 走看看