0%

Tomcat类加载器

Tomcat类加载器

java本身的类加载器是双亲委派机制,而tomcat的应用场景是需要web应用类库隔离的,为了避免应用包之间相互影响,所以java原生的双亲委派是不可以在tomcat中使用了

tomcat类加载器

可以看到tomcat实现了自己的类加载器模型,在catalina.properties中进行配置

1
2
3
4
5
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"

server.loader=

shared.loader=

只有指定了server.loader和shared.loader才会真正建立Catalina类加载器和Shared类加载器的实例

  • Common 以System为父类加载器,位于tomcat应用服务器顶层的公用类加载器,使用common.loader进行配置。可以负责加载Tomcat应用服务器内部和Web应用均可见的类,如Servlet规范相关包以及一些通用的工具包
  • Catalina 以common为父加载器,用于加载tomcat应用服务器的类加载器,使用server.loader进行配置,默认为空,即使用common类加载器加载应用服务器。可以负责加载只有Tomcat应用服务器内部可见的类,这些类对Web应用不可见
  • Shared 以common为父加载器,是所有web应用的父加载器,使用shared.loader进行配置,默认为空,即使用common类加载器作为web应用的父加载器。可以负责加载web应用共享的类,但是对Tomcat自己不可见
  • Web应用 以Shared为父加载器,加载/WEB-INF/classes目录下的未压缩的class和资源文件以及/WEB-INF/lib下的jar包,只对当前web应用可见,tomcat和其他web应用不可见

加载过程

当进行类加载时,除JVM基础类库外,会首先尝试通过当前类加载器加载,然后才会进行委派。Servlet规范相关的API禁止通过Web应用类加载器加载。因此,不要在web应用中包含这些包。如servlet-api.jar

WebappClassLoaderBase#loadClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

synchronized (getClassLoadingLock(name)) {

Class<?> clazz = null;

// (0) Check our previously loaded local class cache
// 在本地 cache 查找该类是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {

if (resolve)
resolveClass(clazz);
return (clazz);
}

// (0.1) Check our previously loaded class cache
// 从系统类加载器的 cache 中查找是否加载过
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}

// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
// 尝试用 ExtClassLoader 类加载器类加载,防止web应用自己的类覆盖了javase的核心类
String resourceName = binaryNameToPath(name, false);

ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}

if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}





// (2) Search local repositories
// 尝试在本地目录搜索 class 并加载

try {
clazz = findClass(name);
if (clazz != null) {

if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}

// (3) Delegate to parent unconditionally
// 尝试用系统类加载器 (也就是 AppClassLoader) 来加载
if (!delegateLoad) {

try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}

throw new ClassNotFoundException(name);
}

WebappClassLoaderBase#findClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Class<?> findClass(String name) throws ClassNotFoundException {

// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
// 在 Web 应用目录下查找类
clazz = findClassInternal(name);

if ((clazz == null) && hasExternalRepositories) {
try {
// 交给父类加载器,走双亲委派的方式
clazz = super.findClass(name);
}catch (RuntimeException e) {

throw e;
}
}
// 父类也没找到,抛出ClassNotFoundException
if (clazz == null) {

throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {

throw e;
}
return (clazz);

}

Web应用类加载器默认加载顺序为

  • 先从缓存中加载
  • 如果没有,则从JVM的Bootstrap类加载器中加载
  • 如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)
  • 如果没有,则从父类加载器加载,父类加载器采用默认的委派方式,加载顺序为System->Common->Shared

当然了,如果就想使用java原生的委派机制怎么办呢

可以看之前文章 tomcat之server配置文件的Loader节点

将delegate改为true即为使用java原生的委派机制,此时的加载顺序为

  • 先从缓存中加载
  • 如果没有,则从JVM的Bootstrap类加载器中加载
  • 如果没有,则从父类加载器加载,父类加载器采用默认的委派方式,加载顺序为System->Common->Shared
  • 如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)

欢迎关注我的其它发布渠道