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:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes 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中有关类加载器的执行步骤如下:

  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,有兴趣的读者可自行阅读源码。

时间: 2024-10-07 14:02:08

Tomcat源码分析——类加载体系的相关文章

Tomcat源码分析——启动与停止服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

Tomcat源码分析之—具体启动流程分析

从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息.Catalina.base信息,在initClassLoaders方法中初始化类加载器,然后通过反射初始化org.apache.catalina.startup.Catalina作为catalina守护进程: 一.load Bootstrap中load流程: 反射调用Catalina的load方法

Tomcat 源码分析(转)

Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件,连接器和容器,所谓连接器就是一个http请求过来了,连接器负责接收这个请求,然后转发给容器.容器即servlet容器,容器有很多层,分别是Engine,     Host,Context,Wrapper.最大的容器Engine,代表一个servlet引擎,接下来是Host,代表一个虚拟机,然后是Context,代表一个应用,Wrapper对应一个servlet.从连接器     传过来连接后,容器便会顺序经过上面的容器,最

Spring源码分析——BeanFactory体系之抽象类、类分析(二)

上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之抽象类.类分析(一),今天继续分析. 一.工厂Bean注册支持——FactoryBeanRegistrySupport 废话不多说,直接看我注释的源码: /* * Copyright 2002-2012 the original author or authors. * * Licensed und

Tomcat源码分析——请求原理分析(下)

前言 本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过<TOMCAT源码分析——请求原理分析(上)>和<TOMCAT源码分析——请求原理分析(中)>.在<TOMCAT源码分析——请求原理分析(中)>一文我简单讲到了Pipeline,但并未完全展开,本文将从Pipeline开始讲解请求原理的剩余内容. 管道 在Tomcat中管道Pipeline是一个接口,定义了使得一组阀门Valve按照顺序执行的规范,Pipeline中定义的接口如下: getBas

Tomcat源码分析——Session管理分析(上)

前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对于使用Tomcat作为Web容器的大部分开发人员而言,Tomcat是如何实现Session标记用户和管理Session信息的呢? 概要 Session Tomcat内部定义了Session和HttpSession这两个会话相关的接口,其类继承体系如图1所示. 图1 Session类继承体系 图1中额

Tomcat源码分析——server.xml文件的加载与解析

前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析进行分析. 加载过程分析 Bootstrap的load方法用于加载tomcat的server.xml,实际是通过反射调用Catalina的load方法,代码如下: /** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call

TOMCAT源码分析(启动框架)

建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, 是不那么容易掌握TOMCAT的框架的. 所以得实践.实践.再实践. 建议下载一份TOMCAT的源码, 调试通过, 然后单步跟踪其启动过程. 如果有不明白的地方, 再来查阅本文, 看是否能得到帮助. 我相信这样效果以及学习速度都会好很多! 1. Tomcat的整体框架结构 Tomcat的基本框架, 分为4个层次. Top Level Elements: Server Service Connector HTTP AJP Conta

tomcat源码分析前的准备工作

Tomcat源码学习前的准备工作 注:由于网上的帖子大部分没有配套的图片和错误的分析,所有费了半天劲整理了此篇博客,希望大家少走弯路吧 下面我们就开始我们的Tomcat源码学习之旅. 1. 下载Tomcat6.0的源代码 首先,我们得下载Tomcat6.0的源代码.Tomcat源代码的版本控制工具不是CVS,而是Subversion,如果您的机器上没有安装Subversion,请从http://subversion.tigris.org/servlets/ProjectDocumentList?