本篇文章我们来探索一下Tomcat的类加载机制,如果我们搜Tomcat的类加载机制,绝大多数结果都会给出答案——打破双亲委托机制。但是感觉很多文章介绍的都不是很清楚,所以本篇文章就从我的理解上来分析一下Tomcat的类加载机制,希望能讲的明白。
关于JVM类加载机制,我们在之前的文章Java编程拾遗『Java ClassLoader工作机制』 已经介绍过,有兴趣的可以去了解一下。
1. 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();
2. 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中的类,而去加载$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属性指定的路径中的类文件。默认情况下,它会按顺序去下列路径中去加载:
$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加载的类(如上所述)
如果你的应用配置了<loader delegate="true"/>
,那么查找顺序就会变为:
- 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. 源码分析
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"/>
,所以默认的类加载顺序为:
- JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
- 应用的/WEB-INF/classes目录
- 应用的/WEB-INF/lib/*.jar
- SystemClassloader加载的类
- CommonClassloader(sharedClassLoader)加载的类
跟JVM默认的双亲委托机制不同,Tomcat会优先加载Web应用目录下的类, 只要该类不覆盖JRE核心类。
参考链接:
1. Tomcat源码
2. 《深入了解Tomcat&Jetty》