Tomcat源码解读系列(四)——Tomcat类加载机制概述

  • Post author:
  • Post category:其他


声明:源码版本为

Tomcat 6.0.35



在本系列的第二篇文章中,曾经介绍过在

Tomcat

启动时会初始化类加载器(




ClassLoader


),来处理整个

Web

工程中

Class

的加载问题。



类加载机制是

Java

平台中相当重要的核心技术,待笔者有所积累后会再次讨论这个话题。在一般的业务开发中我们可能较少接触和使用

ClassLoader

,但是在进行框架级程序开发时,设计良好的类加载机制能够实现更好地模块划分和更优的设计,如

Java

模块化技术

OSGi

就是通过为每个组件声明独立的类加载器来实现组件的动态部署功能。在

Tomcat

的代码实现中,为了优化内存空间以及不同应用间的类隔离,

Tomcat

通过内置的一些类加载器来完成了这些功能。





Java

语言中,

ClassLoader

是以父子关系存在的,

Java

本身也有一定的类加载规范。在

Tomcat

中基本的

ClassLoader

层级关系如下图所示:






Tomcat

启动的时候,会初始化图示所示的类加载器。而上面的三个类加载器:

CommonClassLoader



CatalinaClassLoader



SharedClassLoader

是与具体部署的

Web

应用无关的,而

WebappClassLoader

则对应

Web

应用,每个

Web

应用都会有独立的类加载器,从而实现类的隔离。



我们首先来看

Tomcat

的初始化,在

Bootstrap



init

方法中,会调用

initClassLoaders

方法,该方法负责前图中前三个类加载器的初始化:

private void initClassLoaders() {
        try {
            //初始化CommonClassLoader
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                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);
        }
    }

我们可以看到,此书初始化了三个类加载器,并且

catalinaLoader



sharedLoader

都以

commonLoader

作为父类加载器,在这个方法中,将核心的业务交给了

createClassLoader

方法来实现:

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        //读取配置属性,相关的配置属性在catalina.properties文件中
        String value = CatalinaProperties.getProperty(name + ".loader");
        //如果没有对应的配置,将不会创建新的类加载器,而是返回传入的父类加载器
        if ((value == null) || (value.equals("")))
            return parent;

       //解析得到的配置文件,确定本ClassLoader要加载那些目录下的资源和JAR包等
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken();

          //此处省略的代码为将配置文件中的${catalina.base}、${catalina.home}等变量转
//换为绝对路径

        //格式化得到的位置路径和类型
        String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
        Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
 //生成真正的类加载器 
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (locations, types, parent);

       //以下的代码为将生成的类加载器注册为MBean

        return classLoader;

    }

而每个类加载器所加载的路径或

JAR

是在

catalina.properties

文件中定义的,默认的配置如下:

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=

按照默认的配置,

catalinaLoader



sharedLoader

的配置项为空,因此不会创建对应的

ClassLoader

,而只会创建

CommonClassLoader

,该类加载器对应的

Java

实现类为:

org.apache.catalina.loader. StandardClassLoader

,该类继承自

org.apache.catalina.loader. URLClassLoader

,有关

Tomcat

基础类都会有该类加载器加载。例如在

Bootstrap



init

方法中,会调用

Catalina

类的

init

方法来完成相关操作:

public void init() throws Exception{

       //将当前线程的类加载器设置为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

       //使用catalinaLoader来加载Catalina类
        Class startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        //调用Catalina的setParentClassLoader方法,设置为sharedLoader
        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);

        catalinaDaemon = startupInstance;
    }

以上为基础的三个类加载器的初始化过程。在每个

Web

应用初始化的时候,

StandardContext

对象代表每个

Web

应用,它会使用

WebappLoader

类来加载

Web

应用,而

WebappLoader

中会初始化

org.apache.catalina.loader. WebappClassLoader

来为每个

Web

应用创建单独的类加载器,在上一篇文章中,我们介绍过,当处理请求时,容器会根据请求的地址解析出由哪个

Web

应用来进行对应的处理,进而将当前线程的类加载器设置为请求

Web

应用的类加载器。让我们看一下

WebappClassLoader

的核心方法,也就是

loadClass

public synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        Class clazz = null;

        //首先检查已加载的类
        // (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);
        //Tomcat允许按照配置来确定优先使用本Web应用的类加载器加载还是使用父类
//加载器来进行类加载,此处先使用父类加载器进行加载
        // (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 = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //使用本地的类加载器进行加载
        // (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) {
            ;
        }
        //如果没有特殊配置的话,使用父类加载器加载类
        // (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 = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //若最终类还是没有找到,抛出异常
        throw new ClassNotFoundException(name);

    }

以上就是

Web

应用中类加载的机制。在默认情况下,

WebappClassLoader

的父类加载器就是

CommonClassLoader

,但是我们可以通过修改

catalina.properties

文件来设置

SharedClassLoader

,从而实现多个

Web

应用共用类库的效果。