How tomcat works——8 类加载器(Loader)

概述

我们在前面的章节中已经实现了一个简单的加载器,用它来加载 servlet 类。本章会介绍标准的web应用加载器,简单地说就是加载器,在Catalina中。servlet容器需要定制加载器,而不能简单地使用系统的类加载器,因为系统类加载器不应该信任正在运行的servlet。如果像前面章节中那样使用系统的加载器来加载 servlet 和其它需要的类,这样 servlet 就可以进入 Java 虚拟机 CLASSPATH 环境下面的任何类和类库,这就违背了安全。Servlet 只允许访问 WEB-INF/目录及其子目录下面的类以及部署在WEB-INF/lib目录下的类库。这就是为什么一个servlet容器需要一个自己的加载器。在servlet容器中每个web应用(context)都有自己的加载器。该加载器遵守一些特定的规则来加载类。在 Catalina 中,加载器使用org.apache.catalina.Loader 接口表示。

Tomcat 需要自己的加载器的另一原因是:它需要支持在 WEB-INF/classes或WEB-INF/lib 目录下一个类被改变时会重新加载它。Tomcat 的加载器实现中使用一个单独线程来检查 servlet 和支持类文件的时间戳。要支持类的自动加载功能,一个加载器类必须实现 org.apache.catalina.loader.Reloader 接口。

本章的第1节先简要的回顾下 Java 的类加载机制。接下来介绍了加载器必须要实现的 Loader 接口,然后是 Reloader 接口。接下来看到的是加载器的实现,最后本章使用一个程序示范了如何使用 Tomcat 类的加载器。

本章广泛使用2个术语:存储库(repository)和资源(resources)。 存储库是将由类装入器查找的地方。 资源一词指的是类装载器中的一DirContext对象,其文档基指向上下文的文档基础(The term resources refers to a DirContext object in a class loader whose document base points to the context’s document base.)。

8.1 java类加载器

在每次创建一个 Java 类实例时,必须先将其类加载到内存中。Java 虚拟机(JVM)使用类加载器来加载类。Java 加载器在 Java 核心类库和 CLASSPATH 环境下面的所有类中查找类。如果需要的类找不到,会抛出java.lang.ClassNotFoundException 异常。

从 J2SE1.2 开始,JVM 使用了3种类加载器:bootstrap 类加载器、extension类加载器和 system 类加载器。这3个加载器是父子关系,其中 bootstrap 类加载器在顶端,而 system 加载器在结构的最低层。

其中 bootstrap 类加载器用于引导 JVM,一旦调用 java.exe 程序,bootstrap类加载器就开始工作。因此,它必须使用本地代码(native)实现,然后加载 JVM 需要的类到函数中。另外,它还负责加载所有的 Java 核心类,如 java.lang 和 java.io包。另外 bootstrap 类加载器还会查找核心类库如 rt.jar、i18n.jar 等,搜索哪些库取决于JVM的版本和操作系统。

extension 类加载器负责加载标准扩展目录下面的类。这样就可以使得编写程序变得简单,因为把 JAR 文件拷贝到扩展目录下面,类加载器会自动的在下面查找。不同的供应商提供的扩展类库是不同的,Sun 公司的 JVM 的标准扩展目录是/jdk/jre/lib/ext。

system 加载器是默认的加载器,它在环境变量 CLASSPATH 目录下面查找相应的类。

这样,JVM 使用哪个类加载器呢?答案在于“委派模型”(delegation model),这是出于安全原因。每次一类需要加载,system 类加载器首先调用。但是,它不会马上加载类。相反,它委派该任务给它的父类-extension 类加载器。extension类加载器也把任务委派给它的父类 bootstrap 类加载器。因此,bootstrap 类加载器总是首先加载一个类。如果 bootstrap 类加载器不能找到所需要的这个类,则extension 类加载器会尝试加载。如果extension加载器也失败,system 类加载器将执行加载任务 。 如 果system类加载器找不到类 ,那么将抛出java.lang.ClassNotFoundException 异常。为什么需要这样的往返模式?

“委派模型”对于安全性是非常重要的。如你所知,可以使用安全管理器来限制访问某个目录。现在,有人带着恶意的意图写出一类叫做 java.lang.Object,可用于访问任何在硬盘上的目录。因为 JVM 的信任 java.lang.Object 类,所以它不会关注这方面的活动(自定义的java.lang.Object类随意操作访问)。因此,如果自定义 java.lang.Object 被允许加载的话,那么安全管理器将很容易瘫痪。

幸运的是,这将不会发生,因为“委派模型”会阻止这种情况的发生。下面是它的工作原理。

当自定义 java.lang.Object 类在程序中被调用时,system 类加载器将该请求委派给 extension 类加载器,然后委派给 bootstrap 类加载器。这样 bootstrap类加载器先搜索核心库,找到标准的 java.lang.Object 并实例化它。这样,自定义 java.lang.Object 类永远不会被加载。

Java 类加载机制的优势在于我们可以通过扩展java.lang.ClassLoader 抽象类来扩展自己的类加载器。Tomcat需要自定义自己的类加载器原因包括以下内容:

1》要制定类加载器的某些特定规则
2》缓存以前加载过的类
3》预先加载类以备使用

8.2 Loader接口

在 Web 应用程序中加载 servlet 和其他类需要遵循一些规则。例如,在一个应用程序中 Servlet 可以使用部署到 WEB-INF/classes 目录和其任何子目录下面的类。然而,servlet 不能访问其它类,即使这些类是在运行 Tomcat 所在的 JVM的 CLASSPATH 中。此外,一个 servlet 只能访问 WEB-INF/lib 目录下的类库,而不能访问其它目录下面的。

一个 Tomcat 类加载器表示一个 Web 应用程序加载器,而不是一个类加载器。一个加载器必须实现 org.apache.catalina.Loader 接口。加载器的实现是使用一定制的类加载器 org.apache.catalina.loader.WebappClassLoader类。我们可以使用Loader接口的getClassLoader()方法在Web加载器中获取ClassLoader。

其中值得一提的是 Loader 接口定义了一系列方法跟存储库协作。Web应用程序的WEB-INF / classes和WEB-INF / lib是要作为存储库添加的目录。Loader 接口的addReposity() 方法用于添加一个库,findRepositories() 方法用于返回所有库。

一个 Tomcat 的加载器通常和一个上下文(Context)相关联,通过Loader 接口的getContainer()和 setContainer() 方法建立此关联。一个加载器还可以支持重新加载,如果在上下文中的一个或多个类已被修改。这样,servlet 程序员可以重新编译servlet 或辅助类,新类将被重新加载而不需要重新启动 Tomcat 加载。为了达到重新加载目的,Loader 接口有modified()方法。在加载器的实现中,如果在其库中一个或多个类已被修改,modified()方法必须返回 true,因此需要重新加载。然而,一个加载器不会重新加载它自己。代替的是,它调用Context接口的reload()来进行重新加载。另外两种方法,setReloadable() 和 getReloadable(),用于确定加载器中是否可以使用重加载。默认 情 况 下 , 在 StandardContext 实 现 中(org.apache.catalina.core.StandardContext 类将在第 12 章讨论)重载机制并未启用。因此,要使得上下文启动重载机制,需要在 server.xml 文件添加一些元素如下:

<Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>

另外,一个加载器的实现可以确定是否委派给父加载器类。为了实现这一点,Loader 接口提供了 getDelegate() 和 setDelegate() 方法。

Loader 接口如 Listing8.1 所示

Listing 8.1: The Loader interface

package org.apache.catalina;
import java.beans.PropertyChangeListener;

public interface Loader {
    public ClassLoader getClassLoader();
    public Container getContainer();
    public void setContainer(Container container);
    public DefaultContext getDefaultContext();
    public void setDefaultContext(DefaultContext defaultContext);
    public boolean getDelegate();
    public void setDelegate(boolean delegate);
    public String getInfo();
    public boolean getReloadable();
    public void setReloadable(boolean reloadable);
    public void addPropertyChangeListener(PropertyChangeListener listener);
    public void addRepository(String repository);
    public String[] findRepositories();
    public boolean modified();
    public void removePropertyChangeListener(PropertyChangeListener listener);
}

Catalina 提供了 org.apache.catalina.loader.WebappLoader 作为 Load 接口的实现。WebappLoader 对象包含一个org.apache.catalina.loader.WebappClassLoader 类的实例,该类扩展了Java.netURLClassLoader 类。

注意:每当与加载器相关联的容器需要servlet类,即当它的invoke()方法被调用时,容器首先调用加载器的getClassLoader()方法获取类加载器。 然后容器调用类加载器的loadClass()方法加载servlet类。 更多详情将在第11章“StandardWrapper”中介绍。

Loader 接口和它的实现类的结构图如图 8.1

这里写图片描述
Figure 8.1: The Loader interface and its implementation

8.3 Reloader接口

要支持自动重新加载,一个加载器的实现必须org.apache.catalina.loader.Reloader 接口,该接口如 Listing8.2。

Listing 8.2: The Reloader interface

package org.apache.catalina.loader;

public interface Reloader {
    public void addRepository(String repository);
    public String[] findRepositories ();
    public boolean modified();
}

Reloader 接口里最重要的方法是 modified() 方法,在 web 应用程序中的servlet 或任何支持类被修改时该方法返回 true。addRepository()方法用于添加一个存储库,findRepositories()方法用于返回所有存储库。

8.4 WebappLoader类

org.apache.catalina.loader.WebappLoader 类是 Loader 接口的实现,它表示一 web 应用程序的加载器,负责给 web 应用程序加载类。WebappLoader 创建一个 org.apache.catalina.loader.WebappClassLoader 类的实例作为它的类加载器。像其它Catalina 组件一样,WebappLoader 实现了org.apache.catalina.Lifecycle 接口,可有其相关联容器启动和停止。WebappLoader类也实现java.lang.Runnable接口,以便它可以专用一个线程反复调用其类装载器的modified()方法。如果 modified() 方法返回 true,WebappLoader 实例通知它的关联容器(这里容器是指Context)。类通过Context重新加载自己,而不是 WebappLoader。Context如何实现该功能的,会在第 12 章“StandardContext”中介绍。

WebappLoader 类的 start()方法被调用时,将会完成下面几项重要任务:
1》创建一个类加载器
2》设置存储库(repositories)
3》设置类路径(class path)
4》设置访问权限
5》开启一个新线程用来进行自动加载

在接下来的子章节中会讨论这些任务。

8.4.1 创建一个类加载器

对于加载类,WebappLoader实例使用内部类加载器。 我们可以回忆一下Loader接口的讨论:这个接口提供了getClassLoader()方法,但没有setClassLoader()方法。 因此,我们不能实例化类加载器并将其传递给WebappLoader。 那么,这是不是意味着WebappLoader缺乏灵活性去和一非默认类加载器工作昵?

答案肯定是不。 WebappLoader提供getLoaderClass()和setLoaderClass()方法来获取和更改其私有变量loaderClass的值。该变量是一以String 类型表示的加载器类名称。 默认情况下,loaderClass的值为org.apache.catalina.loader.WebappClassLoader。 如果我们愿意,我们自己可以创建继承于WebappClassLoader的类加载器,并调用setLoaderClass()强制WebappLoader使用此自定义类加载器。 否则,当它启动时,WebappLoader将调用其私有方法createClassLoader()创建WebappClassLoader实例。 此方法在Listing 8.3中给出。

Listing 8.3: The createClassLoader method

private WebappClassLoader createClassLoader()
        throws Exception {

        Class clazz = Class.forName(loaderClass);
        WebappClassLoader classLoader = null;

        if (parentClassLoader == null) {
            // Will cause a ClassCast is the class does not extend WCL, but
            // this is on purpose (the exception will be caught and rethrown)
            classLoader = (WebappClassLoader)clazz.newInstance();
        } else {
            Class[] argTypes = { ClassLoader.class };
            Object[] args = { parentClassLoader };
            Constructor constr = clazz.getConstructor(argTypes);
            classLoader = (WebappClassLoader) constr.newInstance(args);
        }

        return classLoader;

    }

也可以不使用 WebappClassLoader 而使用其它类加载器。注意,但是createClassLoader()方法返回值类型是 WebappClassLoader,所以我们的类必须继承 WebappClassLoader,否则该方法会抛出异常。

8.4.2 设置存储库

WebappLoader 的 start() 方法会调用 setRepositories()方法来给类加载器添加一个库。WEB-INF/classes 目录传递给加载器 addRepository()方法;而 WEB-INF/lib传递给加载器的 setJarPath()方法。这样,类加载器就可以从 WEB-INF/classes 目录和 WEB-INF/lib 目录下面部署的类库(jar)里加载类。

8.4.3 设置类路径

该任务由 start()方法调用 setClassPath()方法完成,setClassPath ()方法会给ServletContext分配一个 String 类型属性来保存 Jasper JSP 编译的类路径。该内容暂且先不做讨论。

8.4.4 设置访问权限

如果 Tomcat 使用了安全管理器,setPermissions() 方法会给必要的目录添加访问权限,例如 WEB-INF/classes 和 WEB-INF/lib。如果不使用管理器,该方法则立刻返回。

8.4.5 开启一个新线程用来自动加载

WebappLoader 支持自动重载。如果 WEB-INF/classes 或 WEB-INF/lib 目录中的类被重新编译过,在不重启 Tomcat 的情况下必须自动重新载入这些类。为了实现这个目的,WebappLoader 有一个单独的线程每隔 x 秒会检查文件源的时间戳。x 值由checkInterval 变量定义,默认值是 15,也就是每隔 15 秒会进行一次检查是否需要自动重载。该类还提供了getCheckInterval()和setCheckInterval()两个方法来访问或设置 checkInterval 值。

在 Tomcat4 中,WebappLoader 实现了 java.lang.Runnable 接口来支持自动重载。WebappLoader 对 run 方法的实现如 Listing8.3 所示:

Listing 8.3: The run method

public void run() {

        if (debug >= 1)
            log("BACKGROUND THREAD Starting");

        // Loop until the termination semaphore is set
        while (!threadDone) {

            // Wait for our check interval
            threadSleep();

            if (!started)
                break;

            try {
                // Perform our modification check
                if (!classLoader.modified())
                    continue;
            } catch (Exception e) {
                log(sm.getString("webappLoader.failModifiedCheck"), e);
                continue;
            }

            // Handle a need for reloading
            notifyContext();
            break;

        }

        if (debug >= 1)
            log("BACKGROUND THREAD Stopping");

    }

注意:
在 Tomcat5 中检查类是否被修改的任务由org.apache.catalina.core.StandardContext 类的 backgroundProcess()方法完成。该方法会被 org.apache.catalina.core.ContainerBase 类中一个专门的线程周期性的调用,ContainerBase 是 StandardContext 类的父类。注意 ContainerBase 类的内部类 ContainerBackgroundProcessor实现了Runnable 接口。

Listing8.3 所示的 run()方法作为该线程的核心部分,包括一个 while 循环直到started 变量为 false。该 while 循环完成了下面的工作:
1》休眠由 checkInterval 变量定义的一段时间
2》检查是否有类被改变,如有则调WebappLoader实例的modified()方法,否则继续循环
3》如果一个类被修改了,调用私有方法 notifyContext()来让跟WebappLoader 实例相关联的Context重新载入

方法 notifyContext 如 Listing8.4 所示:

Listing 8.4: The notifyContext method

private void notifyContext() {
    WebappContextNotifier notifier = new WebappContextNotifier();
    (new Thread(notifier)).start();
}

方法 notifyContext()并不是直接调用 Context 接口中的 reload()方法,它首先初始化一个内部类 WebappContextNotifier 的实例,并把它传递给一个线程对象,调用它的 start()方法。这样重载的执行就由另一个线程完成,WebappContextNotifier 类如 Listing8.5 所示:

Listing 8.5: The WebappContextNotifier inner class

protected class WebappContextNotifier implements Runnable {
    public void run() {
        ((Context) container).reload();
    }
}

当 WebappContextNotifier 类实例被传递给另一个线程,该线程的start() 方法被唤醒时,WebappContextNotifier 实例的 run() 方法会被执行。然后,run()方法调用 Context 接口的 reload() 方法。可以在第 12 章看到org.apache.catalina.core.StandardContext 类是如何实现 reload() 方法的。

8.5 WebappClassLoader类

类 org.apache.catalina.loader.WebappClassLoader 表示在一个 web 应用程序中使用的加载器。WebappClassLoader 类继承了 java.net.URLClassLoader 类,该类在前面章节中用于加载 Java 类。

WebappClassLoader 的设计是出于优化和安全方面考虑。例如它缓存了以前加载的类来改进性能。它也缓存没有找到的类的名称,以便于下一次同名称类被请求加载时,可以直接抛出 ClassNotFound 异常。WebappClassLoader 在资源库源列表(repositories)和特定的 JAR 文件中查找类。

处于安全性考虑,WebappClassLoader 类不允许一些特定的类被加载。这些类被存储在一个 String 类型的数组中,现在仅仅有一个成员。

private static final String[] triggers = {"javax.servlet.Servlet" // Servlet API};

此外,不允许加载属于这些包和其下子包的类,而不必先委派给系统类装载器:

private static final String[] packageTriggers = {
    "javax", // Java extensions
    "org.xml.sax", // SAX 1 & 2
    "org.w3c.dom", // DOM 1 & 2
    "org.apache.xerces", // Xerces 1 & 2
    "org.apache.xalan" // Xalan
};

接下来让我们看看该类是如何实现缓存和加载的。

8.5.1 缓存

为了提高性能,当一个类被加载时会被放到缓存中,这样下次需要加载该类时可直接从缓存中调用即可。缓存由 WebappClassLoader 类实例自己管理。另外,java.lang.ClassLoader 维护了一个 Vector,可以避免前面加载过的类被当做垃圾回收掉。在这里,缓存被该超类管理。

每一个可以被加载的类(放在 WEB-INF/classes 目录下的类文件或者 JAR 文件)都当做被WebappClassLoader加载的一个资源。org.apache.catalina.loader.ResourceEntry 类表示一个资源。一个 ResourceEntry实例通过一byte数组来保存/表示类的信息,最后一个修改日期,清单(如果资源来自JAR文件)等。

ResourceEntry 类如 Listing8.6 所示:

Listing 8.6: The ResourceEntry class.

package org.apache.catalina.loader;

import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;

public class ResourceEntry {

    public long lastModifled = -1;

    // Binary content of the resource.
    public byte[] binaryContent = null;

    public Class loadedClass = null;

    // URL source from where the object was loaded.
    public URL source = null;

    // URL of the codebase from where the object was loaded.
    public URL CodeBase = null;

    public Manifest manifest = null;

    public Certificate[] certificates = null;
}

所有缓存的资源被存放在一个叫做 resourceEntries 的 HashMap 中,键值为资源名,所有找不到的资源都被放在一个名为 notFoundResources 的 HashMap 中。

8.5.2 加载类

当加载一个类时,WebappClassLoader 类遵循以下规则:
1》所有加载过的类都要进行缓存,所以首先需要检查本地缓存
2》 如果在本地缓存中找不到,请检查缓存,即调用java.lang.ClassLoader类findLoadedClass()
3》如果在两个缓存中都找不到,请使用系统的类加载器来阻止Web应用程序覆盖J2EE类
4》如果使用了安全管理器,检查该类是否允许加载,如果不允许加载,则抛出 ClassNotFoundException 异常
5》如果委派标志打开或该类属于 trigger 包中,则使用父加载器来加载类,如果父加载器为 null,则使用系统加载器加载
6》从当前存储源中加载类
7》如果在当前存储源中找不到该类并且没有使用委派标志,则使用父类加载器。如果父类加载器为 null,则使用系统加载器
8》如果该类仍然找不到,则抛出 ClassNotFoundException 异常

8.5.3 应用Demo

本章应用Demo演示了如何使用一个跟Context相关联的 WebappLoader 加载器。一Context的标准实现是 org.apache.catalina.core.StandardContext,所以该应用程序使用了 StandardContext 类。但是关于 StarndardContext 类在 12章中才会详细讨论。这里不需要了解该类的细节,只需要知道它有些监听器和可以触发的事件即可,例如 START_EVENT 和 STOP_EVENT,监听器必须实现org.apache.catalina.lifecycle.LifecycleListener 接口并且调用StandardContext 类的 setConfigured()方法。在该应用程序中,使用ex08.pyrmont.core.SimpleContextConfig 类表示监听器,如 Listing8.6 所示。

Listing 8.6: The SimpleContextConfig class

package ex08.pyrmont.core;

import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

public class SimpleContextConfig implements LifecycleListener {
    public void lifecycleEvent(LifecycleEvent event) {
        if (Lifecycle.START_EVENT.equals(event.getType())) {
            Context context = (Context) event.getLifecycle();
            context.setConfigured(true);
        }
    }
}

我们要做的是初始化一个 StandardContext 和 SimpleContextConfig 对象,然后调用 org.apache.catalina.Lifecycle 接口的 addLifecycleListener()方法把SimpleContextConfig注册给StandardContext。Lifecycle接口已在第6章讨论过。

另外,该应用程序使用了前面章节的类:SimplePipeline, SimpleWrapper和SimpleWrapperValve。

应用程序可以使用PrimitiveServlet和ModernServlet进行测试,但是这一次使用StandardContext规定servlet被存储在WEB-INF / classes应用目录下。应用程序目录称为myApp,应在第一次部署可下载ZIP文件时创建好了。设置系统属性 catalina.base 的值为 user.dir,这样可以告诉 StandardContext实例在哪里查找应用程序目录。

System.setProperty("catalina.base", System.getProperty("user.dir"));

实际上,它是 Bootstrap 类的第一行。然后main()方法初始化连接器:

Connector connector = new HttpConnector();

然后如前章Demo中一样初始化两个 servlet 的两个包装器,如下:

Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");

然后创建了一个 StandardContext 实例并设置path和DocBase:

Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");

这样做如同 在server.xml中做如下配置:

<Context path="/myApp" docBase="myApp"/>

然后,两个包装器被添加到context中,并且添加映射关系,这样context就可以找
到包装器:

context.addChild(wrapper1);
context.addChild(wrapper2);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");

接下来是初始化一个监听器并且在context中注册:

LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);

接下来初始化该context相关联的 WebappLoader:

Loader loader = new WebappLoader();
context.setLoader(loader);

然后建立默认连接器跟context的关联,然后调用连接器的initialize()和 start() 方法,接下来是执行context的 start()方法,这样 servlet 容器就可以工作了:

connector.setContainer(context);
try {
    connector.initialize();
    ((Lifecycle) connector).start();

    ((Lifecycle) context).start();

接下来的几行是打印出docBase和加载器中所有repositorie的值:

// now we want to know some details about WebappLoader
WebappClassLoader classLoader = (WebappClassLoader)
loader.getClassLoader();

System.out.println("Resources' docBase: " +
((ProxyDirContext)classLoader.getResources ()).getDocBase());

String[] repositories = classLoader.findRepositories();
for (int i=0; i<repositorles.length; i++) {
    System.out.println(" repository: " + repositories[i]);
}

当运行应用程序时会显示下面几行:

Resources' docBase: C:\HowTomcatWorks\myApp
repository: /WEB-INF/classes/

docBase 的值可能会有不同,这取决于该应用程序的部署运行环境。

最后,该应用程序等待用户输入终止命令来停止应用程序:

// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();

8.5.4 运行Demo

在 windows 下,可以在工作目录下面如下运行该程序:

java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ex08.pyrmont.startup.Bootstrap

在 Linux 下,使用冒号分开两个库:

java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ex08.pyrmont.startup.Bootstrap

调用PrimitiveServlet,可以使用下面的 URL 来请求:

http://localhost:8080/Primitive

调用ModernServlet,可以使用下面的 URL 来请求:

http://localhost:8080/Modern

8.6 小结

Web应用程序加载器,或简单的加载器,是Catalina中最重要的组件。 加载器负责加载类,因而使用内部类加载器。 这个内部类加载器是一个自定义类,Tomcat使用它来将特定规则应用于应用程序上下文中的类加载。 此外,自定义类加载器支持缓存,可以检查是否有一个或更多的类已经被修改。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值