Tomcat源码解读『Tomcat类加载机制』

本篇文章我们来探索一下Tomcat的类加载机制,如果我们搜Tomcat的类加载机制,绝大多数结果都会给出答案——打破双亲委托机制。但是感觉很多文章介绍的都不是很清楚,所以本篇文章就从我的理解上来分析一下Tomcat的类加载机制,希望能讲的明白。

关于JVM类加载机制,我们在之前的文章Java编程拾遗『Java ClassLoader工作机制』 已经介绍过,有兴趣的可以去了解一下。

1. Tomcat类加载机制要考虑的问题

Tomcat作为Servlet容器,它负责加载我们的Servlet类,此外它还负责加载Servlet所依赖的 JAR 包。并且Tomcat本身也是也是一个Java程序,因此它需要加载自己的类和依赖的JAR包,所以可能要考虑这几个问题:

  1. 假如在Tomcat中运行了两个Web应用程序,两个Web应用中有同名的 Servlet,但是功能不同,Tomcat需要同时加载和管理这两个同名的Servlet 类,保证它们不会冲突,也就是说,Web应用之间的类需要隔离
  2. 假如两个Web应用都依赖同一个第三方的JAR包,比如Spring,那Spring的JAR包被加载到内存后,Tomcat要保证这两个Web应用能够共享,也就是说Spring的JAR包只被加载一次,否则随着依赖的第三方JAR包增多,JVM 的内存会膨胀
  3. Tomcat自身也是一个Java程序,需要隔离Tomcat本身的类和Web应用的类,避免相互影响,比如Web应用中定义了一个同名类导致Tomcat本身的类无法加载

所以,Tomcat是如何来解决这些问题的?答案是通过设计多层次的类加载器。

1.1 WebAppClassLoader

首先来看第一个问题,假如我们使用JVM默认AppClassLoader来加载Web应用,AppClassLoader只能加载一个 Servlet 类,在加载第二个同名 Servlet 类时,AppClassLoader会返回第一个Servlet类的Class实例,这是因为在 AppClassLoader看来,同名的Servlet类只被加载一次。

因此Tomcat的解决方案是自定义一个类加载器WebAppClassLoader,并且给每个Web应用创建一个类加载器实例。我们知道,Context容器组件对应一个Web应用,因此,每个Context容器负责创建和维护一个WebAppClassLoader加载器实例,这背后的原理是,不同的加载器实例加载的类被认为是不同的类,即使它们的类名相同。这就相当于在Java虚拟机内部创建了一个个相互隔离的Java类空间,每一个Web应用都有自己的类空间,Web应用之间通过各自的类加载器互相隔离。

1.2 SharedClassLoader

再来看第二个问题,本质需求是两个Web应用之间怎么共享库类,并且不能重复加载相同的类。我们知道,在双亲委托机制里,各个子加载器都可以通过父加载器去加载类,那么把需要共享的类放到父加载器的加载路径下应该就可以了,应用程序也正是通过这种方式共享JRE的核心类。因此Tomcat的设计者又加了一个类加载器SharedClassLoader,作为WebAppClassLoader的父加载器,专门来加载Web应用之间共享的类。如果WebAppClassLoader自己没有加载到某个类,就会委托父加载器SharedClassLoader去加载这个类,SharedClassLoader会在指定目录下加载共享类,之后返回给WebAppClassLoader,这样共享的问题就解决了。

1.3 CatalinaClassloader

第三个问题,如何隔离Tomcat本身的类和Web应用的类。我们知道,要共享可以通过父子关系,要隔离那就需要兄弟关系了。兄弟关系就是指两个类加载器是平行的,它们可能拥有同一个父加载器,但是两个兄弟类加载器加载的类是隔离的。基于此Tomcat又设计一个类加载器CatalinaClassloader,专门来加载Tomcat自身的类。

如果Tomcat和各Web应用之间需要共享一些类时该怎么办?

1.4 CommonClassLoader

老办法,还是再增加一个CommonClassLoader,作为CatalinaClassloader和SharedClassLoader的父加载器。CommonClassLoader能加载的类都可以CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和 SharedClassLoader能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

1.5 线程类加载器

在JVM的实现中有一条隐含的规则,默认情况下,如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载。比如 Spring 作为一个Bean工厂,它需要创建业务类的实例,并且在创建业务类实例之前需要加载这些类。Spring是通过调用Class.forName来加载业务类的,我们来看一下forName的源码:

public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

可以看到在forName的函数里,会用调用者也就是Spring的加载器去加载业务类。

前面提到,Web应用之间共享的JAR包可以交给SharedClassLoader来加载,从而避免重复加载。Spring作为共享的第三方JAR包,它本身是由SharedClassLoader 来加载的,Spring又要去加载业务类,按照前面那条规则,加载Spring的类加载器也会用来加载业务类,但是业务类在Web应用目录下,不在SharedClassLoader的加载路径下,这该怎么办呢?

于是线程上下文加载器登场了,它其实是一种类加载器传递机制。为什么叫作“线程上下文加载器”呢,因为这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用。因此Tomcat为每个Web应用创建一个WebAppClassLoarder类加载器,并在启动Web应用的线程里设置线程上下文加载器,这样Spring在启动时就将线程上下文加载器取出来,用来加载Bean。Spring取线程上下文加载的代码如下:

ClassLoader cl = Thread.currentThread().getContextClassLoader();

2. Tomcat类加载机制默认实现

上面我们介绍了几种Tomcat的自定义类加载器,但其实除了上述几种自定义类加载器,Tomcat自身也是Java应用,肯定也需要JDK类加载器来加载。Tomcat相关的类加载器的加载范围如下:

2.1 BootstrapClassLoader

BootstrapClassLoader负责加载JVM提供的基础的运行时类(即rt.jar)以及${JAVA\_HOME}/jre/lib/ext下的类,按照之前JVM类加载器的介绍,其实这是BootstrapExtension这两个类加载器的功能。到底是一个类加载器还是两个类加载器,是由具体的JVM决定的。

2.2 SystemClassLoader

这里说的SystemClassLoader,其实就是之前介绍的AppClassLoader,不同的是在Tomcat的运行环境下,它不再去加载CLASSPATH中的类,而去加载$CATALINA_HOME/bin目录下的3个jar(这里是启动脚本catalina.sh中写死的),主要包括以下三个jar:

  • bootstrap.jar
  • tomcat-juli.jar(如果$CATALINA_BASE/bin目录下也有这个包,会使用$CATALINA_BASE/bin目录下的)
  • commons-daemon.jar

2.3 CommonClassLoader

CommonClassLoader是Tomcat自定义的类加载器,用于加载Tomcat容器自身和Web应用之间需要共享的类。所以该类加载器加载的类,对Tomcat自身和所有Web应用都可见。通常情况下,应用的类文件不应该放在Common ClassLoader中。Common会扫描 $CATALINA_BASE/conf/catalina.properties中common.loader属性指定的路径中的类文件。默认情况下,它会按顺序去下列路径中去加载:

  1. $CATALINA_BASE/lib中未打包的classes和resources
  2. $CATALINA_BASE/lib中的jar文件
  3. $CATALINA_HOME/lib中未打包的classes和resources
  4. $CATALINA_HOME/lib中的jar文件

默认情况下,这些路径下的jar主要有以下这些:

  • annotations-api.jar — JavaEE annotations classes.
  • catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.
  • catalina-ant.jar — Tomcat Catalina Ant tasks.
  • catalina-ha.jar — High availability package.
  • catalina-storeconfig.jar — Generation of XML configuration files from current state
  • catalina-tribes.jar — Group communication package.
  • ecj-.jar — Eclipse JDT Java compiler. el-api.jar — EL 3.0 API. jasper.jar — Tomcat Jasper JSP Compiler and Runtime. jasper-el.jar — Tomcat Jasper EL implementation. jsp-api.jar — JSP 2.3 API. servlet-api.jar — Servlet 3.1 API. tomcat-api.jar — Several interfaces defined by Tomcat. tomcat-coyote.jar — Tomcat connectors and utility classes. tomcat-dbcp.jar — Database connection pool implementation based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP. tomcat-i18n-*.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.
  • tomcat-jdbc.jar — An alternative database connection pool implementation, known as Tomcat JDBC pool. See documentation for more details.
  • tomcat-util.jar — Common classes used by various components of Apache Tomcat.
  • tomcat-websocket.jar — WebSocket 1.1 implementation
  • websocket-api.jar — WebSocket 1.1 API

2.4 WebappClassLoader

每一个部署在Tomcat中的web应用,Tomcat都会为其创建一个WebappClassloader,它会去加载应用WEB-INF/classes目录下所有未打包的classes和resources,然后再去加载WEB-INF/lib目录下的所有jar文件。每个应用的WebappClassloader都不同,因此,它加载的类只对本应用可见,其他应用不可见(这是实现web应用隔离的关键)。

这里我们来介绍一个重要的概念——Tomcat类加载机制打破了双亲委托机制。为什么Tomcat要打破双亲委托机制?

上面我们说了使用JDK默认类加载机制无法解决多个web应用加载同名类的问题,所以自定义了WebAppClassLoader用于解决该问题。其实说到底Tomcat打破JDK自定义类加载器的原因是Servlet规范,优先加载Web应用目录下的类,只要该类不覆盖JRE核心类

从Web应用的视角来看,当有类加载的请求时,class或者resource的查找顺序是这样的:

  1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
  2. 应用的/WEB-INF/classes目录
  3. 应用的/WEB-INF/lib/*.jar
  4. SystemClassloader加载的类(如上所述)
  5. CommonClassloader加载的类(如上所述)

如果你的应用配置了<loader delegate="true"/>那么查找顺序就会变为:

  1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
  2. SystemClassloader加载的类(如上所述)
  3. CommonClassloader加载的类(如上所述)
  4. 应用的/WEB-INF/classes目录
  5. 应用的/WEB-INF/lib/*.jar

可以发现,如果配置了delegate = true,其实WebAppClassLoader的加载机制就是标准的双亲委托机制了。

讲到我们发现,好像少了两个类加载器,CatalinaClassLoader和SharedClassLoader。是因为在Tomcat默认实现中直接使用的是CommonClassLoader作为CatalinaClassLoader和SharedClassLoader。

3. 源码分析

3.1 类加载器构建

org.apache.catalina.startup.Bootstrap#main方法中在创建Bootstrap后会调用bootstrap.init()方法,如下:

/**
 * Initialize daemon.
 * @throws Exception Fatal initialization error
 */
public void init() throws Exception {

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // 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.getConstructor().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);

    catalinaDaemon = startupInstance;
}

这里首先会调用initClassLoaders方法初始化类加载器,然后通过catalinaLoader加载一些类,然后反射实例化Catalina对象,并反射setParentClassLoader方法,将Catalina的parentClassLoader成员变量设置为sharedLoader。

3.2 initClassLoaders

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);
    }
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {

    // 1. 从catalina.properties中获取”name.loader“属性
    // 默认实现中common.loader = ${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
    // server.loader = 
    // shared.loader = 
    String value = CatalinaProperties.getProperty(name + ".loader");
    // 2. 如果在catalina.properties找不到value,直接返回参数的parent
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);

    List<Repository> repositories = new ArrayList<>();

    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(new Repository(repository, RepositoryType.DIR));
        }
    }

    // 3. 创建类加载器
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

结合这两个方法,我们可以得知,默认实现中,CommonClassLoader、CatalinaClassLoader、SharedClassLoader其实是一种类加载器,都是CommonClassLoader(也就是说默认Tomcat容器跟容器的web应用之间未隔离)。

另外需要注意的是,创建CommonClassLoader时,createClassLoader方法parent传参为null,那么CommonClassLoader的父类加载器(类加载器的parent成员)是SystemClassLoader,这部分逻辑在ClassLoaderFactory.createClassLoader中。

到这里我们发现,上述Tomcat自定义类加载还少了一个WebAppClassLoader没有创建,WebAppClassLoader是在Context的启动方法中创建的。

3.3 WebAppClassLoader

StandardContext类的startInterl方法中有如下一段逻辑:

if (getLoader() == null) {
    WebappLoader webappLoader = new WebappLoader();
    webappLoader.setDelegate(getDelegate());
    setLoader(webappLoader);
}

这里创创建的是WebappLoader,并不是我们所说的WebAppClassLoader,那么WebAppLoader和WebAppClassLoader之间有什么关系?WebAppLoader实现了LifeCycle接口,并且内部有一个成员变量classLoader(类型为WebappClassLoaderBase),用于存储真正用于Web服务类加载的类加载器。也就是说,我们可以理解为WebAppLoader是WebAppClassLoader的管理组件。

那么接下来我们来看一下Tomcat如何通过上述这段代码,管理WebAppClassLoader的。首先调用了WebAppLoader的无参构造函数:

/**
 * Construct a new WebappLoader. The parent class loader will be defined by
 * {@link Context#getParentClassLoader()}.
 */
public WebappLoader() {
    this(null);
}

/**
 * Construct a new WebappLoader with the specified class loader
 * to be defined as the parent of the ClassLoader we ultimately create.
 *
 * @param parent The parent class loader
 *
 * @deprecated Use {@link Context#setParentClassLoader(ClassLoader)} to
 *             specify the required class loader. This method will be
 *             removed in Tomcat 10 onwards.
 */
@Deprecated
public WebappLoader(ClassLoader parent) {
    super();
    this.parentClassLoader = parent;
}

从注释中我们也可以知道,这里仅仅是构建了一个WebAppLoader对象,其成员变量parentClassLoader设置为null。但是注释中介绍到,parentClassLoader将被赋值为Context#getParentClassLoader(),那么肯定在其它地方,实现了该赋值逻辑。另外这里再着重介绍一下Context#getParentClassLoader(),该方法返回的其实就是我们上面介绍的在Bootstrap类中反射Catalina的setParentClassLoader设置进去的sharedLoader,同时上面也介绍到sharedLoader初始化时,其实跟commonLoader是一个对象,所以这里最终Context#getParentClassLoader()返回的其实是commonLoader

接下来我们接着看StandardContext#setLoader方法:

public void setLoader(Loader loader) {

    Lock writeLock = loaderLock.writeLock();
    writeLock.lock();
    Loader oldLoader = null;
    try {
        // Change components if necessary
        oldLoader = this.loader;
        if (oldLoader == loader)
            return;
        this.loader = loader;

        // Stop the old component if necessary
        if (getState().isAvailable() && (oldLoader != null) &&
            (oldLoader instanceof Lifecycle)) {
            try {
                ((Lifecycle) oldLoader).stop();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardContext.setLoader.stop"), e);
            }
        }

        // Start the new component if necessary
        if (loader != null)
            loader.setContext(this);
        if (getState().isAvailable() && (loader != null) &&
            (loader instanceof Lifecycle)) {
            try {
                ((Lifecycle) loader).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardContext.setLoader.start"), e);
            }
        }
    } finally {
        writeLock.unlock();
    }

    // Report this property change to interested listeners
    support.firePropertyChange("loader", oldLoader, loader);
}

这里Tomcat启动时调用该方法,肯定会调用到loader的start方法,根据我们之前介绍的LifeCycle机制,这里最终会调用到WebAppLoader的startInternal方法:

protected void startInternal() throws LifecycleException {

    if (log.isDebugEnabled())
        log.debug(sm.getString("webappLoader.starting"));

    if (context.getResources() == null) {
        log.info(sm.getString("webappLoader.noResources", context));
        setState(LifecycleState.STARTING);
        return;
    }

    // Construct a class loader based on our current repositories list
    try {

        classLoader = createClassLoader();
        classLoader.setResources(context.getResources());
        classLoader.setDelegate(this.delegate);

        // Configure our repositories
        setClassPath();

        setPermissions();

        classLoader.start();

        String contextName = context.getName();
        if (!contextName.startsWith("/")) {
            contextName = "/" + contextName;
        }
        ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
                classLoader.getClass().getSimpleName() + ",host=" +
                context.getParent().getName() + ",context=" + contextName);
        Registry.getRegistry(null, null)
            .registerComponent(classLoader, cloname, null);

    } catch (Throwable t) {
        t = ExceptionUtils.unwrapInvocationTargetException(t);
        ExceptionUtils.handleThrowable(t);
        throw new LifecycleException(sm.getString("webappLoader.startError"), t);
    }

    setState(LifecycleState.STARTING);
}

可以看到,核心逻辑就是classLoader的初始化,通过createClassLoader()方法创建类加载器:

private WebappClassLoaderBase createClassLoader()
    throws Exception {

    Class<?> clazz = Class.forName(loaderClass);
    WebappClassLoaderBase classLoader = null;

    if (parentClassLoader == null) {
        parentClassLoader = context.getParentClassLoader();
    } else {
        context.setParentClassLoader(parentClassLoader);
    }
    Class<?>[] argTypes = { ClassLoader.class };
    Object[] args = { parentClassLoader };
    Constructor<?> constr = clazz.getConstructor(argTypes);
    classLoader = (WebappClassLoaderBase) constr.newInstance(args);

    return classLoader;
}
/**
 * The Java class name of the ClassLoader implementation to be used.
 * This class should extend WebappClassLoaderBase, otherwise, a different
 * loader implementation must be used.
 */
private String loaderClass = ParallelWebappClassLoader.class.getName();

我们这里知道,WebAppLoader默认关联的类加载器类型为ParallelWebappClassLoader。然后就是我们上面介绍的,为什么WebAppLoader的成员变量为null,那么将被设置为context#getParentClassLoader(),在这里也给出了答案,我们再createClassLoader()方法中实现了对parentClassLoader的赋值。最后就是调用了ParallelWebappClassLoader的有参构造函数,实现了WebAppLoader关联的类加载器的创建。

public ParallelWebappClassLoader(ClassLoader parent) {
    super(parent);
}

3.3.1 Tomcat是如何打破双亲委托机制的

其实我们经常说的Tomcat打破了双亲委托机制,其实是指自定义类加载器ParallelWebappClassLoader(WebAppClassLoader)打破了双亲委托机制。按照我们之前对类加载器的理解,ParallelWebappClassLoader肯定自定义实现了loadClass方法。但ParallelWebappClassLoader除了重写了loadClass方法,其实还重写了findClass方法,接下来我们来看一下这两个方法。

首先来看一下findClass方法(org.apache.catalina.loader.WebappClassLoaderBase#findClass),为了方便阅读,这里省略了一些catch异常以及日志的一些细节。

public Class<?> findClass(String name) throws ClassNotFoundException {

    // (1) Permission to define this class when using a SecurityManager
    // ……

    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;
    try {
        try {
            // 1. 从Web应用目录下查找类
            clazz = findClassInternal(name);
        } catch(AccessControlException ace) {
            // throw exception
        } catch (RuntimeException e) {
            // throw exception
        }

        // 2. 如果Web应用目录下为找到类,并且WebAppClassLoader指定了额外加载的路径,
        // 则交给父类去指定的额外加载路径去查找类
        if ((clazz == null) && hasExternalRepositories) {
            try {
                clazz = super.findClass(name);
            } catch(AccessControlException ace) {
                // throw exception
            } catch (RuntimeException e) {
                // throw exception
            }
        }
        if (clazz == null) {
            // throw exception
        }
    } catch (ClassNotFoundException e) {
        if (log.isTraceEnabled())
            log.trace("    --> Passing on ClassNotFoundException");
        throw e;
    }

   // log
    return clazz;

}

从这里可以知道,WebAppClassLoader中默认的是直接在Tomcat本地查找Class,但是提供了参数可以控制从父类,或者指定的目录去查找Class。

接下来我们来看一下loadClass方法,如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;

        // 1. 先从本地缓存中查找类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            // ……
            return clazz;
        }

        // 2. 从系统类加载器缓存中查找是否已经加载过
        clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
        if (clazz != null) {
            // ……
            return clazz;
        }

        // ……

        if (tryLoadingFromJavaseLoader) {
            try {
                // 3. 尝试使用javaSE classLoader来加载,避免web应用覆盖核心jre类
                // 这里的javaSE classLoader是ExtClassLoader还是BootstrapClassLoader,要看具体的jvm实现
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    // ……
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // ……

        boolean delegateLoad = delegate || filter(name, true);

        // 3. 如果delegateLoad为true,则先使用parent(sharedLoader\commonLoader)加载
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    // ……
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 4. 在Web应用目录中加载类
        try {
            clazz = findClass(name);
            if (clazz != null) {
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 5. 如果delegateLoad为false,则在web应用目录中加载后,再使用parent(sharedLoader\commonLoader)加载
        if (!delegateLoad) {
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    // ……
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }

    // 6. 如果上述步骤都未加载到Class,抛ClassNotFoundException
    throw new ClassNotFoundException(name);
}

Tomcat默认未配置<loader delegate="true"/>,所以默认的类加载顺序为:

  1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
  2. 应用的/WEB-INF/classes目录
  3. 应用的/WEB-INF/lib/*.jar
  4. SystemClassloader加载的类
  5. CommonClassloader(sharedClassLoader)加载的类

跟JVM默认的双亲委托机制不同,Tomcat会优先加载Web应用目录下的类, 只要该类不覆盖JRE核心类。

参考链接:

1. Tomcat源码

2. 《深入了解Tomcat&Jetty》

3. Tomcat类加载机制分析

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页