Tomcat WebappClassLoader 状态变更

Tomcat 中 WebappClassLoader 用于加载部署到Tomcat中应用的类,WebappClassLoader 有自己的生命周期,处于启动的状态情况下才可以正常的加载Class,否则会抛出相应的异常出来。不同的tomcat版本,处理的方式也不一致,在研发Apptalking Agent 时候遇到此问题,特此做一下记录。

源码分析

以下的代码以 Tomcat 6 源码,参考:
https://github.com/qiunet/tomcat6-Note/blob/master/java/org/apache/catalina/loader/WebappClassLoader.java

Tomcat 7 源码参考:
https://github.com/apache/tomcat/blob/TOMCAT_7_0_42/java/org/apache/catalina/loader/WebappClassLoader.java

加载 Class

如果WebappClassLoader 不是处于started 状态,加载Class的时候会直接抛出 IllegalStateException 异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {

if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class clazz = null;

// Log access to stopped classloader
if (!started) {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.info(sm.getString("webappClassLoader.stopped", name), e);
}
}

WebappClassLoader 启动

在启动Classloader的时候会设置 started 为true,启动后才可以正常的加载 class。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Start the class loader.
*
* @exception LifecycleException if a lifecycle error occurs
*/
public void start() throws LifecycleException {

started = true;
String encoding = null;
try {
encoding = System.getProperty("file.encoding");
} catch (Exception e) {
return;
}
if (encoding.indexOf("EBCDIC")!=-1) {
needConvert = true;
}

}

Tomcat 7 以上的版本区别

Tomcat 7 以上版本的WebappClassLoader 也是有生命周期的,但是不在是以一个Boolean值来标示其是否为启动状态,而是使用一个State枚举(部分Tomcat 8版本 和 Tomcat 7 是一致的)。
详细的可以参考 WebappClassLoader 的父类,WebappClassLoaderBase。
Tomcat 9 源码参考:
https://github.com/apache/tomcat/blob/TOMCAT_9_0_12/java/org/apache/catalina/loader/WebappClassLoaderBase.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum LifecycleState
{
NEW(false, null),
INITIALIZING(false, "before_init"),
INITIALIZED(false, "after_init"),
STARTING_PREP(false, "before_start"),
STARTING(true, "start"),
STARTED(true, "after_start"),
STOPPING_PREP(true, "before_stop"),
STOPPING(false, "stop"),
STOPPED(false, "after_stop"),
DESTROYING(false, "before_destroy"),
DESTROYED(false, "after_destroy"),
FAILED(false, null),
}