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

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

 

posted @ 2017-05-21 17:16  大诚挚  阅读(851)  评论(0编辑  收藏  举报