ClassLoaderFactory.java
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent)
throws Exception {
Set<URL> set = new LinkedHashSet<>();
if (repositories != null) {
for (Repository repository : repositories) {
// 对不同类型的 Repository 对象进行处理,将路径转换为URL类型
// 因为 URL 类型带有明显的协议,比如jar:xxx、file:xxx
}
}
// 将对应的路径组装成 URL
final URL[] array = set.toArray(new URL[set.size()]);
// 在创建 URLClassLoader 需要考虑到 AccessController 的影响
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
private static URL buildClassLoaderUrl(File file) throws MalformedURLException {
String fileUrlString = file.toURI().toString();
fileUrlString = fileUrlString.replaceAll("!/", "%21/"); // 转换成URL编码
return new URL(fileUrlString);
OK,前面介绍了 tomcat 创建类加载器的过程,接下来我们看下 tomcat 类加载器的具体应用场景
WebappClassLoader
在前面,我们介绍了 tomcat 类加载器的设计,每个 webapp 使用单独的类加载器完成我们开发的 webapp 应用程序的类加载,而每一个 webapp 对应一个 WebappClassLoader
。tomcat7 默认使用 WebappClassLoader
类加载器,而 tomcat8 默认使用 ParallelWebappClassLoader
,支持并行加载类的特性,这也算是 tomcat8 做的一些优化吧,而实际上也是利用 jdk 的功能,需要同时满足以下两点才支持并行加载类,并且一旦注册了并行加载的能力,就不能回退了
1、 没有创建调用者的实例
2、 调用者的所有超类(除了类对象)都是并行注册的
基于上面两点,因此,ParallelWebappClassLoader
在 static 代码块中注册并行加载机制,而它的父类 URLClassLoader
父类也是具有并行能力的,关键代码如下所示:
public class ParallelWebappClassLoader extends WebappClassLoaderBase {
static {
boolean result = ClassLoader.registerAsParallelCapable();
if (!result) {
log.warn(sm.getString("webappClassLoaderParallel.registrationFailed"));
}
}
// 省略无关代码...
WebappClassLoader
的类图如下所示,其中 WebappClassLoaderBase
实现了主要的逻辑,并且继承了 Lifecycle
,在 tomcat 组件启动、关闭时会完成资源的加载、卸载操作,例如在 start
过程会读取我们熟悉的 /WEB-INF/classes
、/WEB-INF/lib
资源,并且记录每个 jar 包的时间戳方便重载 jar 包;而在组件 stop
的时候,会清理已经加载的资源;destory
时会显式地触发 URLClassLoader.close()
。这个 Lifecycle
真是无处不在啊
单独的类加载器是无法获取 webapp 的资源信息的,因此 tomcat 引入了 WebappLoader
,便于访问 Context
组件的信息,同时为 Context
提供类加载的能力支持,下面我们分析下 WebappLoader
的底层实现
WebappLoader
我们先来看看 WebappLoader
几个重要的属性,内部持有 Context
组件,并且有个我们熟悉的 reloadable
参数,如果设为 true,则会开启类的热加载机制
public class WebappLoader extends LifecycleMBeanBase
implements Loader, PropertyChangeListener {
private WebappClassLoaderBase classLoader = null; // 默认使用ParallelWebappClassLoader
private Context context = null;
private String loaderClass = ParallelWebappClassLoader.class.getName();
private ClassLoader parentClassLoader = null; // 父加载器,默认为 catalina 类加载器
private boolean reloadable = false; // 是否支持热加载类
private String classpath = null;
在 tomcat 中,每个 webapp 对应一个 StandardContext
,在 start
过程便会实例化 WebappLoader
,并且调用其 start
方法完成初始化,包括创建 ParallelWebappClassLoader
实例,然后,还会启动 Context
的子容器。注意,这两个过程,都会将线程上下文类加载器指定为 ParallelWebappClassLoader
类加载器,在完成 webapp 相关的类加载之后,又将线程上下文类加载器设置为 catalina 类加载器。Context
容器的启动过程,这里便不再重复了,感兴趣的童鞋请查看前面的博文 webapp源码分析
StandardContext.java
protected synchronized void startInternal() throws LifecycleException {
// 实例化 Loader 实例,它是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader); // 使用了读写锁控制并发问题
}
// 将 Loader 中的 ParallelWebappClassLoader 绑定到当前线程中,并返回 catalian 类加载器
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// 如果 Loader 是 Lifecycle 实现类,则启动该 Loader
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
// 设置 ClassLoader 的各种属性
setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets());
// 省略……
// 解除线程上下文类加载器绑定
unbindThread(oldCCL);
oldCCL = bindThread();
// 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是加载 Context 的子容器
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// 启动子容器
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
}
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
而 WebappLoader
在 stop
的时候,会销毁 WebappClassLoader
,并且进行回收,促使 jvm 卸载已加载的类
WebappLoader.java
@Override
protected void stopInternal() throws LifecycleException {
// 省略不相关代码...
if (classLoader != null) {
try {
classLoader.stop();
} finally {
classLoader.destroy();
}
}
classLoader = null; // help gc
Hotswap
我们可以为 Context
组件指定 reloadable
属性,如果设为 true
,tomcat便会启用 Hotswap,定期扫描类文件的变动,如果有变动,则重启 webapp 从而达到 Hotswap
的目的。
这个参数由 Context
指定的,但是会通过 WebappLoader#setContext(Context context)
方法调用,从而传递给 WebappLoader
WebappLoader
提供了后台定时任务的方法,Context
容器会间隔性地进行调用,它用于监听 class
、jar
等文件的变更,一旦有变动,便会对 Context
容器进行 reload
操作
WebappLoader.java
@Override
public void backgroundProcess() {
if (reloadable && modified()) {
try {
// 变更线程上下文类加载器为 webapp 类加载器
Thread.currentThread().setContextClassLoader(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload(); // 重载 webapp
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
}
}
}