我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

前言

  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的类加载体系

  Tomcat实现了自身的AppClassLoader。为便于理解,图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中有关类加载器的执行步骤如下:

    初始化commonLoader、catalinaLoader和sharedLoader; 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器; 线程安全的加载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的处理步骤如下:

    定位资源路径与资源类型; 使用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的步骤如下:

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

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

我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为1000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版百度优化、名注册、主机空间、手机网站建设公众号开发小程序制作、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线: 13820372851,我们会详细为你一一解答你心中的疑难。项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13820372851

上班时间

周一到周五

公司电话

022-26262675

二维码
线
在线留言