tomcat8.5源码学习四之类加载机制
Healthy Mind Lv3

tomcat 的类加载机制 和 Java的 双亲委派机制是相反的

直接看tomcat Bootstrap的 initClassLoader方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}

这里一共初始化了三个 Classloader

commonLoader 是 catalinaLoader和sharedLoader 的父加载器,

在看 createClassLoader 方法

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
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {

String value = CatalinaProperties.getProperty(name + ".loader");
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));
}
}

return ClassLoaderFactory.createClassLoader(repositories, parent);
}

这里是读取 tomcat/conf/catalina.properties 配置文件里配置的 common.loader ,server.loader,shared.loader这几个配置 路径下的jar

主要的是ClassLoaderFactory.createClassLoader(repositories, parent); 这句代码

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
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {

if (log.isDebugEnabled())
log.debug("Creating new class loader");

// Construct the "class path" for this class loader
Set<URL> set = new LinkedHashSet<>();

...

// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}

return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}

这个方法前面很长,主要是各种判断,我们看最后的返回值 AccessController这个是Java 权限的一共类,这里可以不用了解,主要是里面run方法的路径,当parent为空就返回一共新的 URLClassLoader 当 不为空就返回子类加载器,也就是说,这个加载器,和双亲委派机制相反的,Java的双亲委派是,当父加载器不为空的时候选返回父类加载器,去加载类;但是这里确是返回子类加载器。

我们在看一下 Java Classloader的类加载源码

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父类加载器 不为空就 由父类加载器加载class
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

核心逻辑就这段

1
2
3
4
5
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}

而 commonLoader 、 catalinaLoader、sharedLoader 就是 URLClassLoader 类型这个类又是 ClassLoader的子类 是不是我们可以这样猜测,在Tomcat中 这三个顶层 的类加载器之下的类加载机制 还是局部的双亲委派,但是在 这三个顶层类加载器却是逆向的 ,由字类选加载,