Tomcat源码剖析——类加载器
本文解析源码来自于Tomcat8.5.33
本文引用参考文献为《深入理解JVM虚拟机-周志明》、《Tomcat架构解析-刘光瑞》
注:此文为连载文章,后续将更新文章《初始化》
文章目录
概念
Tomcat作为一款开源的轻量级应用服务器,它能够同时为多个Web应用进行服务。Tomcat是运行在JVM上的服务器,那么各个服务之间,如何保障加载的类不会冲突?这就靠的是Tomcat的类加载器设计;
JVM类加载机制
谈及Tomcat的类加载器,那么不得不提一下虚拟机的类加载机制。
定义:JVM把描述类的数据从class文件加载到内存并对数据进行校验、解析,初始化最终形成可以被虚拟机直接使用的Java类型;
流程:具体流程可参考《深入理解JVM虚拟机》中类加载机制章节和本人的相关文章;
加载机制的核心就是类加载器。关于Java的类加载器,不得不提一下Java牛逼的双亲委派模型;
双亲委派原理:除了顶层的启动类加载器外,其余加载器都应有自己的父类加载器。每次收到类加载请求,都会委派给父类加载器完成,若父类加载器无法加载,子加载器才会尝试自己加载。
双亲委派模型
-
启动类加载器(Boostrap Class Loader)
这个类加载器负责加载存放在JAVAHOME\jre\lib目录下,或被
-Xbootclasspath
指定的路径中。JVM能够识别的类库加载到虚拟机的内存中。该加载器无法被java程序直接使用。 -
扩展类加载器(Extension Class Loader)
他负责加载JAVAHOME\jre\lib\ext目录中或指定路径的所有类库。JDK允许用户将具有通用性的类库放置在ext目录里扩展SE的功能。
-
应用程序类加载器(Application/System Class Loader)
是ClassLoader.getSystemClassLoader()返回值。负责加载用户类路径上所有的类库。程序中的默认类加载器。
Tomcat类加载机制特性
首先我们要明确一点,如果一个class文件是由同一个类加载器加载的,那么其Java类型是相同的。如果一个class文件是由两个不同的类加载器加载的,那么该class对应的Java类型则不一致,其实例化出的对象(静态)也肯定不一致;那么一个Tomcat服务器要同时运行多个Web应用,如何保证其加载进内存的jar包正常执行,其需要具备以下四点:
- 隔离性:Web应用类库相互隔离,避免各个服务之间的类库或jar包相互影响;
- 灵活性:每个Web应用的启动和关闭,对应的就是类的加载和销毁是独立灵活的。不会对其他Web应用产生影响;
- 性能:保证Tomcat的执行性能,例如加载新的Web服务和卸载旧的服务时,应稳定且快速完成;
- 共享:Tomcat通过Common类加载器实现了Jar包在应用服务器以及Web应用之间的共享;通过Shared类加载器实现了Jar包在Web应用之间的共享;
Tomcat的类加载器模型
-
Common加载器
Tomcat的顶层共用类加载器,其父类加载器是系统/应用类加载器。
加载路径为
common.loader
,默认指向$CATALINA_HOME/lib下的包;加载:Tomcat应用服务器内部和Web应用均可见的类,例如Servlet规范,通用工具包;
-
Catalina加载器
Tomcat应用服务器的类加载器,其父类加载器是Common加载器。
加载路径为
server.loader
,默认指向空,因此默认CatalinaLoader=CommonLoader;加载:加载Tomcat自身内部可见的类(例如Server,Service,Engine等),对Web应用不可见;目的是为了将Web应用和Tomcat服务器解耦;
-
Shared加载器
是Web应用的父加载器,其父类加载器是Common加载器。
加载路径为
shared.loader
,默认指向空,因此默认SharedLoader=CommonLoader;加载:加载Web应用中共享的类,这些类Tomcat内部不会依赖;
-
Web应用
以Shared架子啊其为父类,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的Jar包。
该类加载器只对当前Web应用可见,对其他Web应用均不可见;
以上类加载器配置均在conf/catalina.properties
文件中
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
# 默认为空
server.loader=
# 默认为空
shared.loader=
默认下的Tomcat类加载器
Tomcat类加载器源码
流程图
1. Bootstrap.init()
Tomcat启动的时候,调用引导类init()方法,第一步就是初始化类加载器;请看方法2;
// Bootstrap.class
public void init() throws Exception {
// 初始化类加载器
// commonLoader: 加载catalina.properties配置文件中common.loader=>加载工作/安装目录下的lib文件
// catalinaLoader: server.loader,默认为空,因此是commonLoader
// sharedLoader: shared.loader,默认为空,因此是commonLoader
initClassLoaders();
// ...
}
2. Bootstrap.initClassLoaders()
在这个方法中,很明显可以看出,我们通过调用createClassLoader()方法,并指定配置文件的键值,去创建类加载器,还能指定其父类加载器;具体看方法3;
// Bootstrap.class
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();
}
// 默认未配置,则返回commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 默认未配置,则返回commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
3. Bootstrap.createClassLoader()
这个函数就是将catalina.properties
文件(上文已粘贴相应内容)中指定的类加载器配置读取,将扫描路径封装成Repository,并交由ClassLoaderFactory0createClassLoader()去处理;见方法4;
其中,因为默认server,shared没有配置,所以直接返回parent,及commonLoader;
// Bootstrap.class
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// 加载工作和安装目录下lib路径的class
String value = CatalinaProperties.getProperty(name + ".loader");
// 这里就是为什么Common,Catalina,Shared为同一个类加载器的原因
if ((value == null) || (value.equals(""))) {
return parent;
}
// 前value: "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
// 后value: "D:\person_project\tomcat8.5/lib","D:\person_project\tomcat8.5/lib/*.jar","D:\person_project\tomcat8.5/lib","D:\person_project\tomcat8.5/lib/*.jar"
// replace 将符号替换成绝对路径
value = replace(value);
List<Repository> repositories = new ArrayList<>();
// 将value,拆成数组
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));
}
}
// 将目录封装成Repository,并根据所扫描的类型坐上标记(*.jar->glob, .jar->jar, other->dir)
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
4. ClassLoaderFactory.createClassLoader()
这里将传入的路径进行校验,将合法的文件转成URL传入AccessController.doPrivileged();
这里Tomcat为保证类扫描加载都能保证权限执行,使用AccessController保证代码可以顺利扫描加载,最终还是调用了new URLClassLoader()去创建类加载器;
// ClassLoaderFactory.class
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
// Construct the "class path" for this class loader
Set<URL> set = new LinkedHashSet<>();
// 扫描路径校验 ...将repositories下的扫描文件生成URL放入set中
// Construct the class loader itself
final URL[] array = set.toArray(new URL[0]);
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);
}
}
});
}
// native 方法
public static native <T> T doPrivileged(PrivilegedAction<T> action);
因此,默认Common,Catalina,Shared为同一个类加载器,都是CommonLoader;
Tomcat的Web应用类加载器
默认加载顺序(delegate=false):
- 从缓存中加载;
- 如果没有,则从JVM的Bootstrap类加载器(根加载器)加载;
- 如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序);
- 如果没有,则从父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序为System,Common,Shared;
开启delegate=true采用委派模式,加载顺序:
- 从缓存中加载;
- 如果没有,则从JVM的Bootstrap类加载器(根加载器)加载;
- 如果没有,则从父类加载器加载(System,Common,Shared);
- 如果没有,则从当前类加载器加载;