本文Tomcat版本基于apache-tomcat-8.5.12.看一下Tomcat的源码Bootstrap类的initClassLoader方法,代码如下:
private void initClassLoaders() {
try {
// 创建commonLoader
// 这里父加载器传递null,但是内部会使用默认的类加载器AppClassLoader作为父加载器.
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,父加载器为commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 创建sharedLoader,父类加载器为commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
通过initClassLoaders方法可以得知上面三个加载器关系.下面具体看一下createClassLoader是如何构造类加载器的.代码如下:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// 获取catalina.properties文件中配置项分别为:common.loader/server.loader/shared.loader
// 用来设置对应类加载器扫描类的路径.默认内容如下:
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
// 依据common.loader/server.loader/shared.loader的配置装饰类加载器所需的扫描路径.
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);
}
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
所以依据上面的代码可知,commonLoader/serverLoader/sharedLoader是同一个ClassLoader.
具体创建类加载器的是ClassLoaderFactory.createClassLoader(repositories, parent);
,具体代码如下:
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<>();
if (repositories != null) {
for (Repository repository : repositories) {
if (repository.getType() == RepositoryType.URL) {
URL url = buildClassLoaderUrl(repository.getLocation());
if (log.isDebugEnabled())
log.debug(" Including URL " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.DIR) {
File directory = new File(repository.getLocation());
directory = directory.getCanonicalFile();
if (!validateFile(directory, RepositoryType.DIR)) {
continue;
}
URL url = buildClassLoaderUrl(directory);
if (log.isDebugEnabled())
log.debug(" Including directory " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.JAR) {
File file=new File(repository.getLocation());
file = file.getCanonicalFile();
if (!validateFile(file, RepositoryType.JAR)) {
continue;
}
URL url = buildClassLoaderUrl(file);
if (log.isDebugEnabled())
log.debug(" Including jar file " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.GLOB) {
File directory=new File(repository.getLocation());
directory = directory.getCanonicalFile();
if (!validateFile(directory, RepositoryType.GLOB)) {
continue;
}
if (log.isDebugEnabled())
log.debug(" Including directory glob "
+ directory.getAbsolutePath());
String filenames[] = directory.list();
if (filenames == null) {
continue;
}
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
file = file.getCanonicalFile();
if (!validateFile(file, RepositoryType.JAR)) {
continue;
}
if (log.isDebugEnabled())
log.debug(" Including glob jar file "
+ file.getAbsolutePath());
URL url = buildClassLoaderUrl(file);
set.add(url);
}
}
}
}
// 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]);
}
// (1)
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
依据(1)可以创建classLoader时parent=null,这时候使用了单个参数的URLClassLoader创建了URLClassLoader类加载器作为commonLoader,而URLClassLoader默认的父加载器为AppClassLoader.那么URLClassLoader是什么呢?看一下下面的UML类图:
到这里总结一下,默认情况下Tomcat的commonLoader/serverLoader/sharedLoader是同一个加载器,其类查找路径都是都一个地方.其实catalinaLoader主要的工作是加载Tomcat本身启动所需要的类,而sharedLoader是下文将要说的WebAppclassloader的父类,所以作用是加载所有应用都需要的类,而commonLoader做为sharedLoader/catalinaLoader的父类,自然设计目的是为了加载二者共享的类.所以如果能恰当的使用Tomcat设计的这种策略,修改catalina.properties中三种加载器类加载路径,就会真正达到这种设计效果.