zoukankan      html  css  js  c++  java
  • Tomcat源码分析——类加载体系

    前言

      Tomcat遵循J2EE规范,实现了Web容器。很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解。此外,Tomcat还根据Java虚拟机规范实现了经典的双亲委派模式的类加载体系。本文基于Tomcat7.0的Java源码,对其类加载体系进行分析。

    概述

      本节简单介绍Java虚拟机规范中提到的主要类加载器:

    • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
    • Extended Loader:加载libext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:projects estprojclasses HelloWorld。
    • AppClassLoader:加载System.getProperty("java.class.path")所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。

    Tomcat的类加载体系

      除了JVM规范提到的以上三种类加载器之外,Tomcat还实现了自身的类加载体系。为便于理解,图1展示了Tomcat的类加载体系,各个类加载器之间不是继承关系,而是一种委派关系。

    图1  Tomcat的类加载体系

    这里对图1所示的类加载体系进行介绍:

    • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现;
    • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
    • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
    • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
    • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见。

    源码分析

       commonLoader、catalinaLoader和sharedLoader是在Tomcat容器初始化的的过程刚刚开始,即调用Bootstrap的init方法时创建的。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身的class。Bootstrap的init方法的部分如代码清单1所示。

    代码清单1

        /**
         * Initialize daemon.
         */
        public void init()
            throws Exception
        {
    
            // Set Catalina path
            setCatalinaHome();
            setCatalinaBase();
    
            initClassLoaders();
    
            Thread.currentThread().setContextClassLoader(catalinaLoader);
    
            SecurityClassLoad.securityClassLoad(catalinaLoader);
            // 省略后边的代码

      代码清单1中有关类加载器的执行步骤如下:

    1. 初始化commonLoader、catalinaLoader和sharedLoader;
    2. 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
    3. 线程安全的加载class。

    初始化类加载器分析

      initClassLoaders方法的实现如代码清单2所示。

    代码清单2

        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) {
                log.error("Class loader creation threw exception", t);
                System.exit(1);
            }
        }

      从代码清单2可以看到initClassLoaders调用createClassLoader方法来创建commonLoader、catalinaLoader和sharedLoader,我们来看看createClassLoader的实现,见代码清单3。

    代码清单3

        private ClassLoader createClassLoader(String name, ClassLoader parent)
            throws Exception {
    
            String value = CatalinaProperties.getProperty(name + ".loader");
            if ((value == null) || (value.equals("")))
                return parent;
    
            ArrayList<String> repositoryLocations = new ArrayList<String>();
            ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();
            int i;
     
            StringTokenizer tokenizer = new StringTokenizer(value, ",");
            while (tokenizer.hasMoreElements()) {
                String repository = tokenizer.nextToken();
    
                // Local repository
                boolean replace = false;
                String before = repository;
                while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
                    replace=true;
                    if (i>0) {
                    repository = repository.substring(0,i) + getCatalinaHome() 
                        + repository.substring(i+CATALINA_HOME_TOKEN.length());
                    } else {
                        repository = getCatalinaHome() 
                            + repository.substring(CATALINA_HOME_TOKEN.length());
                    }
                }
                while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
                    replace=true;
                    if (i>0) {
                    repository = repository.substring(0,i) + getCatalinaBase() 
                        + repository.substring(i+CATALINA_BASE_TOKEN.length());
                    } else {
                        repository = getCatalinaBase() 
                            + repository.substring(CATALINA_BASE_TOKEN.length());
                    }
                }
                if (replace && log.isDebugEnabled())
                    log.debug("Expanded " + before + " to " + repository);
    
                // Check for a JAR URL repository
                try {
                    new URL(repository);
                    repositoryLocations.add(repository);
                    repositoryTypes.add(ClassLoaderFactory.IS_URL);
                    continue;
                } catch (MalformedURLException e) {
                    // Ignore
                }
    
                if (repository.endsWith("*.jar")) {
                    repository = repository.substring
                        (0, repository.length() - "*.jar".length());
                    repositoryLocations.add(repository);
                    repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
                } else if (repository.endsWith(".jar")) {
                    repositoryLocations.add(repository);
                    repositoryTypes.add(ClassLoaderFactory.IS_JAR);
                } else {
                    repositoryLocations.add(repository);
                    repositoryTypes.add(ClassLoaderFactory.IS_DIR);
                }
            }
    
            String[] locations = repositoryLocations.toArray(new String[0]);
            Integer[] types = repositoryTypes.toArray(new Integer[0]);
     
            ClassLoader classLoader = ClassLoaderFactory.createClassLoader
                (locations, types, parent);
    
            // 省略无关代码
    
            return classLoader;
    
        }

      createClassLoader的处理步骤如下:

    1. 定位资源路径与资源类型;
    2. 使用ClassLoaderFactory创建类加载器org.apache.catalina.loader.StandardClassLoader。

      需要注意的是,Tomcat默认只会指定commonLoader(通过common属性,默认值为${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar),catalinaLoader和sharedLoader实际也是commonLoader。属性catalina.home默认为Tomcat的根目录。

    安全加载class分析

      首先回头看看SecurityClassLoad.securityClassLoad(catalinaLoader)的实现,见代码清单4。

    代码清单4

        public static void securityClassLoad(ClassLoader loader)
            throws Exception {
    
            if( System.getSecurityManager() == null ){
                return;
            }
            
            loadCorePackage(loader);
            loadLoaderPackage(loader);
            loadSessionPackage(loader);
            loadUtilPackage(loader);
            loadJavaxPackage(loader);
            loadCoyotePackage(loader);        
            loadTomcatPackage(loader);
        }

      securityClassLoad方法主要负责加载Tomcat容器所需的class,包括:

    • Tomcat核心class,即org.apache.catalina.core路径下的class;
    • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
    • Tomcat有关session的class,即org.apache.catalina.session路径下的class;
    • Tomcat工具类的class,即org.apache.catalina.util路径下的class;
    • javax.servlet.http.Cookie;
    • Tomcat处理请求的class,即org.apache.catalina.connector路径下的class;
    • Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class;

      以加载Tomcat核心class的loadCorePackage方法为例(见代码清单5),查看其实现。

    代码清单5

        private final static void loadCorePackage(ClassLoader loader)
            throws Exception {
            String basePackage = "org.apache.catalina.";
            loader.loadClass
                (basePackage +
                 "core.ApplicationContextFacade$1");
            loader.loadClass
                (basePackage +
                 "core.ApplicationDispatcher$PrivilegedForward");
            loader.loadClass
                (basePackage +
                 "core.ApplicationDispatcher$PrivilegedInclude");
            loader.loadClass
                (basePackage +
                "core.AsyncContextImpl");
            loader.loadClass
                (basePackage +
                "core.AsyncContextImpl$AsyncState");
            loader.loadClass
                (basePackage +
                "core.AsyncContextImpl$DebugException");
            loader.loadClass
                (basePackage +
                "core.AsyncContextImpl$1");
            loader.loadClass
                (basePackage +
                "core.AsyncContextImpl$2");
            loader.loadClass
                (basePackage +
                "core.AsyncListenerWrapper");
            loader.loadClass
                (basePackage +
                 "core.ContainerBase$PrivilegedAddChild");
            loader.loadClass
                (basePackage +
                 "core.DefaultInstanceManager$1");
            loader.loadClass
                (basePackage +
                 "core.DefaultInstanceManager$2");
            loader.loadClass
                (basePackage +
                 "core.DefaultInstanceManager$3");
            loader.loadClass
                (basePackage +
                 "core.DefaultInstanceManager$4");
            loader.loadClass
                (basePackage +
                 "core.DefaultInstanceManager$5");
            loader.loadClass
                (basePackage +
                 "core.ApplicationHttpRequest$AttributeNamesEnumerator");
        }

    WebappClassLoader 的实现分析

      至此,我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,启动StandardContext的方法startInternal的实现见代码清单6。

    代码清单6

        /**
         * Start this component and implement the requirements
         * of {@link LifecycleBase#startInternal()}.
         *
         * @exception LifecycleException if this component detects a fatal error
         *  that prevents this component from being used
         */
        @Override
        protected synchronized void startInternal() throws LifecycleException {
    
            // 省略前边无关的代码 
    
            if (getLoader() == null) {
                WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
                webappLoader.setDelegate(getDelegate());
                setLoader(webappLoader);
            }
           // 省略中间无关的代码 
           // Start our subordinate components, if any
           if ((loader != null) && (loader instanceof Lifecycle))
                ((Lifecycle) loader).start(); 
           // 省略后边无关的代码 
        }

      代码清单6的最后会调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单7。

    代码清单7

        /**
         * Start associated {@link ClassLoader} and implement the requirements
         * of {@link LifecycleBase#startInternal()}.
         *
         * @exception LifecycleException if this component detects a fatal error
         *  that prevents this component from being used
         */
        @Override
        protected void startInternal() throws LifecycleException {
            
            // 省略无关代码// Construct a class loader based on our current repositories list
            try {
    
                classLoader = createClassLoader();
                classLoader.setResources(container.getResources());
                classLoader.setDelegate(this.delegate);
                classLoader.setSearchExternalFirst(searchExternalFirst);
                if (container instanceof StandardContext) {
                    classLoader.setAntiJARLocking(
                            ((StandardContext) container).getAntiJARLocking());
                    classLoader.setClearReferencesStatic(
                            ((StandardContext) container).getClearReferencesStatic());
                    classLoader.setClearReferencesStopThreads(
                            ((StandardContext) container).getClearReferencesStopThreads());
                    classLoader.setClearReferencesStopTimerThreads(
                            ((StandardContext) container).getClearReferencesStopTimerThreads());
                    classLoader.setClearReferencesThreadLocals(
                            ((StandardContext) container).getClearReferencesThreadLocals());
                }
    
                for (int i = 0; i < repositories.length; i++) {
                    classLoader.addRepository(repositories[i]);
                }

      最后我们看看WebappLoader的createClassLoader方法的实现,见代码清单8。

    代码清单8

        /**
         * Create associated classLoader.
         */
        private WebappClassLoader createClassLoader()
            throws Exception {
    
            //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader
            Class<?> clazz = Class.forName(loaderClass);
            WebappClassLoader classLoader = null;
    
            if (parentClassLoader == null) {
                parentClassLoader = container.getParentClassLoader();
            }
            Class<?>[] argTypes = { ClassLoader.class };
            Object[] args = { parentClassLoader };
            Constructor<?> constr = clazz.getConstructor(argTypes);
            classLoader = (WebappClassLoader) constr.newInstance(args);
    
            return classLoader;
    
        }

      代码清单8中的parentClassLoader实际就是sharedLoader,即org.apache.catalina.loader.StandardClassLoader。由此也证实了图1中的WebappClassLoader的父类加载器是sharedLoader。至此,整个Tomcat的类加载体系构建完毕。最后我们看看WebappClassLoader(见代码清单9)是如何实现以及部署在tomcat中的各个webapp的资源是如何隔离的?

    代码清单9

        @Override
        public synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
    
            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 = system.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;
                        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);
                ClassLoader loader = parent;
                if (loader == null)
                    loader = system;
                try {
                    clazz = Class.forName(name, false, loader);
                    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);
                ClassLoader loader = parent;
                if (loader == null)
                    loader = system;
                try {
                    clazz = Class.forName(name, false, loader);
                    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);
    
        }

      从代码清单9,可以看到WebappClassLoader加载class的步骤如下:

    1. 从之前加载的class的缓存中查找;
    2. 委托sun.misc.Launcher$AppClassLoader加载class;
    3. 安全检查通过后,委托父类加载器org.apache.catalina.loader.StandardClassLoader加载class;
    4. WebappClassLoader自己加载class。

      有关WebappClassLoader的findClass方法的实现很简单,其中主要调用findClassInternal方法来加载webapp自身路径下的class,有兴趣的读者可自行阅读源码。

    如需转载,请标明本文作者及出处——作者:jiaan.gja,本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/4860432.html 
    道生一,一生二,二生三,三生万物。
  • 相关阅读:
    用PHP写一个最简单的解释器Part4(写一个最简单的脚本语言)
    Java 引用传递
    Java 收集的代码 transient
    Java 继承 执行顺序
    Java 静态类 static
    Java 多态 虚方法
    Mongodb 安装
    入手Intel 750
    Intellij IDEA 创建控制台项目,断点调试快捷方式
    IntelliJ IDEA 的 Java 热部署插件 JRebel 安装及使用
  • 原文地址:https://www.cnblogs.com/jiaan-geng/p/4860432.html
Copyright © 2011-2022 走看看