Tomcat(七)类加载机制

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

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

假如在Tomcat中运行了两个Web应用程序,两个Web应用中有同名的 Servlet,但是功能不同,Tomcat需要同时加载和管理这两个同名的Servlet 类,保证它们不会冲突,也就是说,Web应用之间的类需要隔离
假如两个Web应用都依赖同一个第三方的JAR包,比如Spring,那Spring的JAR包被加载到内存后,Tomcat要保证这两个Web应用能够共享,也就是说Spring的JAR包只被加载一次,否则随着依赖的第三方JAR包增多,JVM 的内存会膨胀
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();

二 Tomcat类加载机制默认实现

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

2.1 BootstrapClassLoader

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

2.2 SystemClassLoader

这里说的SystemClassLoader,其实就是之前介绍的AppClassLoader,不同的是在Tomcat的运行环境下,它不再去加载CLASSPATH中的类,而去加载 C A T A L I N A H O M E / b i n 目 录 下 的 3 个 j a r ( 这 里 是 启 动 脚 本 c a t a l i n a . s h 中 写 死 的 ) , 主 要 包 括 以 下 三 个 j a r : b o o t s t r a p . j a r t o m c a t − j u l i . j a r ( 如 果 CATALINA_HOME/bin目录下的3个jar(这里是启动脚本catalina.sh中写死的),主要包括以下三个jar: bootstrap.jar tomcat-juli.jar(如果 CATALINAHOME/bin3jarcatalina.shjarbootstrap.jartomcatjuli.jarCATALINA_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属性指定的路径中的类文件。默认情况下,它会按顺序去下列路径中去加载:

$CATALINA_BASE/lib中未打包的classes和resources
$CATALINA_BASE/lib中的jar文件
$CATALINA_HOME/lib中未打包的classes和resources
$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的查找顺序是这样的:

JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
应用的/WEB-INF/classes目录
应用的/WEB-INF/lib/*.jar
SystemClassloader加载的类(如上所述)
CommonClassloader加载的类(如上所述)
如果你的应用配置了,那么查找顺序就会变为:

JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
SystemClassloader加载的类(如上所述)
CommonClassloader加载的类(如上所述)
应用的/WEB-INF/classes目录
应用的/WEB-INF/lib/*.jar
可以发现,如果配置了delegate = true,其实WebAppClassLoader的加载机制就是标准的双亲委托机制了。

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

三 源码分析

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默认未配置,所以默认的类加载顺序为:

JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
应用的/WEB-INF/classes目录
应用的/WEB-INF/lib/*.jar
SystemClassloader加载的类
CommonClassloader(sharedClassLoader)加载的类
跟JVM默认的双亲委托机制不同,Tomcat会优先加载Web应用目录下的类, 只要该类不覆盖JRE核心类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_42242792

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值