为了集中放置,所以在收集资料的过程中将所有的信息都直接以草稿的方式放在了CSDN上,在收集了一周的资料后,今早因为CSDN的操作不熟练导致全给删了,我。。。。。。。。。。。
1. 概述
虽然一年多前就开始尝试阅读Tomcat源码源码,但Tomcat源码还是比较庞大的,所以对Tomcat的理解一直比较零散,没有形成体系。而最近碰到一些ClassLoader导致的问题,所以决定趁机研究下Tomcat中对ClassLoader的应用。本次研究我们按照时间线来进行讲解。
2. ClassLoader初始化
2.1 Bootstrap
类
Bootstrap
作为Tomcat启动逻辑的入口,其中涉及到ClassLoader的代码主要有两处,分别是 init
和initClassLoaders
方法:
Bootstrap.init
方法
initClassLoaders(); // 重点关注这个
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
Bootstrap.initClassLoaders
方法
这里初始化完毕的URLClassLoader是遵从ClassLoader的 级委托接待机制的。
// 该方法负责给类级字段`commonLoader`, `catalinaLoader`, `sharedLoader` 赋值。
private void initClassLoaders() {
try {
// 这里的common.loader,类型为URLClassLoader
// 其parent classloader为 sun.misc.Launcher$AppClassLoader;
// 注意该ClassLoader是遵从ClassLoader的 上级委托接待机制 的
// 就是这个loader负责加载Server和Context
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, sharedLoader等于上面的commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
我们通过JVISUALVM来看看Tomcat运行时的环境变量
sun.misc.Launcher$AppClassLoader
负责加载的是System.getProerty(“java.class.path”)
下的Class。- 而common.loader则负责加载 ${common.loader}指示下的Class。
- 所以加载Server,Context还是common.loader生成的URLClassLoader。
2.2 Catalina.createStartDigester
方法
- 该方法的作用的就是构建一个用于解析XML配置文件(默认是
conf/server.xml
)的Digester 组件。 - 为相关容器(Container)中的ClassLoader相关字段赋值:
Catalina.SetParentClassLoaderRule
。 负责为Engine
设置parentClassLoader
(该字段定义在容器基类ContainerBase
中)。- 位于
HostRuleSet
中的CopyParentClassLoaderRule
。设置Host
的parentClassLoader
(该字段定义在容器基类ContainerBase
中)。 - 以上作为
parentClassLoader
值来源的是加载Catalina的类 ——sun.misc.Launcher$AppClassLoader
。 - 对于
Context
其覆写了继承自ContainerBase
的getParentClassLoader
方法。
- 注意该方法中还有这样一句相关代码
digester.setUseContextClassLoader(true);
,结合上面的Thread.currentThread().setContextClassLoader(catalinaLoader);
我们可以得出结论:Server,Context等Class都是使用上面的catalinaLoader (URLClassLoader类型,且父ClassLoader为加载Catalina
的类 ——sun.misc.Launcher$AppClassLoader
)来进行加载。
3. StandardContext
类
- 关注的重点还是这个。
其覆写了继承自基类
ContainerBase
的getParentClassLoader
方法。@Override public ClassLoader getParentClassLoader() { // 如果自身显式定义了, 则直接返回 if (parentClassLoader != null) return (parentClassLoader); // 如果显式设置了privileged为true, 则返回加载自身的ClassLoader,也就是common.loader代表的URLClassLoader,注意该URLClassLoader的parent ClassLoader为sun.misc.Launcher$AppClassLoader。 if (getPrivileged()) { return this.getClass().getClassLoader(); } else if (parent != null) { // 如果parent不为null, 在StandardContext的情况下, 这是肯定满足的。所以这里的parent为Host,因此这里的返回值也是 sun.misc.Launcher$AppClassLoader return (parent.getParentClassLoader()); } return (ClassLoader.getSystemClassLoader()); }
其中涉及到的是Tomcat自身的三个相关类
WebappLoader
Loader
WebappClassLoaderBase
ClassLoader
DefaultInstanceManager
InstanceManager
我们首先要看的是startInternal
方法
//
// 代码实在太多了,故进行了非必要的删减
//
// ------------------------------------ 初始化Loader
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
...
// Binding thread
// 将Loader关联的ClassLoader绑定到上下文
// 这里返回的应该是在Bootstrap.java的init()方法中绑定的catalinaLoader[默认加载路径在conf/catalina.properties的common.loader对应的值]
ClassLoader oldCCL = bindThread();
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
// since the loader just started, the webapp classloader is now created.
// By calling unbindThread and bindThread in a row, we setup the current Thread CCL to be the webapp classloader
unbindThread(oldCCL); // 将common.loader的CCL绑定回当前线程
// setup the current Thread CCL to be the webapp classloader [说得已经比较清楚了,将Loader相关的CCL再次关联当前线程]
// 返回common.loader
oldCCL = bindThread();
...
// Unbinding thread
// 再次将common.loader的CCL绑定回当前线程
unbindThread(oldCCL);
// Binding thread
oldCCL = bindThread(); // 再再一次将Loader关联的ClassLoader绑定到上下文
// ------------------------------------ 初始化InstanceManager
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
}
// 回调ServletContainerInitializer相关类(@HandlesTypes标注的实现类)
//使用StandardContext的InstanceManager字段来负责实例化了Servlet,Filter,Listener
// listenerStart()
// filterStart()
// loadOnStartup(findChildren())
// 相关的解释, 我们放到InstanceManager节点中
...
// Unbinding thread
unbindThread(oldCCL); // 再再次将common.loader的CCL绑定回当前线程
...
// Close all JARs right away to avoid always opening a peak number of files on startup
if (getLoader() instanceof WebappLoader) {
((WebappLoader) getLoader()).closeJARs(true);
}
现在让我们来具体看看WebappClassLoaderBase,WebappLoader,InstanceManager
4. WebappLoader
类
Loader
接口的定义显示其并不负责实际的Class加载工作。- 实现了
Lifecycle
接口, 也正是在startInternal
方法实例化并配置WebappClassLoader
。 Loader
的主要作用还是进行相关配置; 对Class进行加载的逻辑还是在WebappClassLoader
中。
5. WebappClassLoader
类
- 绝大部分逻辑位于其基类
WebappClassLoaderBase
中。 - 主要关注的是覆写继承自基类的
loadClass
,findClass
(可以翻阅下《深入分析Java Web技术内幕(修订版)》P157)。 loadClass
和findClass
方法中的代码量比较大,有兴趣的读者可以自己去研究下。 涉及到
- 自定义在加载class时的各类规则.
- 缓存之前已加载过的class.
- 预加载某些class.
基类WebappClassLoaderBase
中有一些字段值得关注
1. triggers
, Servlet规范里要求相关类不允许被Servlet容器加载。 这里就是相关实现处了。 而我们经常会在控制台看到的那句话:validateJarFile(xxx) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: xxx
, 正是位于其validateJarFile
方法中 - 第3373行
2. packageTriggers
在进行 委托给父classloader之前,不允许自身先加载,类的集合。
3. 等等
6. InstanceManager
类
- 实现类为
DefaultInstanceManager
。 几个关键字段
classLoader
,containerClassLoader
的赋值情况// ------------------- DefaultInstanceManager类的构造函数 // catalinaContext实际类型为: StandardContext , // 所以这个classLoader应该就是WebappClassLoader classLoader = catalinaContext.getLoader().getClassLoader(); privileged = catalinaContext.getPrivileged(); // containerClassLoader就是加载StandardContext的ClassLoader, 结合之前的digester.setUseContextClassLoader(true); // 所以这个containerClassLoader应该就是common.loader(即URLClassLoader,其praent Classloader是sun.misc.Launcher$AppClassLoader) this.containerClassLoader = containerClassLoader;
就是这个
InstanceManager
类负责加载所有的Servlet
,Filter
,Listener
等。StandardContext.listenerStart()
。这里可以发现使用了StandardContext
的InstanceManager
字段来负责实例化了Listener
。StandardContext.filterStart()
。这里最终可以追踪到ApplicationFilterConfig
类的构造函数, 其也是使用了StandardContext
的InstanceManager
字段来负责实例化了Filter
。loadOnStartup(findChildren())
。这里可以追踪到StandardWrapper
的loadServlet
方法, 其正是使用了StandardContext
的InstanceManager
字段来负责实例化了Servlet
。
本类中负责加载Servlet,Filter,Listener的逻辑为其内部的 loadClass
方法,
// ----------------------- DefaultInstanceManager.loadClass
/*
大概逻辑是
1. 使用containerClassLoader进行加载 (这个containerClassLoader应该就是common.loader(即URLClassLoader,其praent Classloader是sun.misc.Launcher$AppClassLoader))
1. "org.apache.catalina" package内的Class.
2. ContainerServlet的实现类
2. 剩下的都使用classLoader来进行加载 (这个classLoader应该就是WebappClassLoader )
3. 再结合之前的测试, 可以得出结论: 我们自定义的类都是由WebappClassLoader来加载的.
*/
protected Class<?> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
// 特定的 package 内
if (className.startsWith("org.apache.catalina")) {
return containerClassLoader.loadClass(className);
}
try {
Class<?> clazz = containerClassLoader.loadClass(className);
// 关于这个ContainerServlet, 本篇文章就不涉及了
if (ContainerServlet.class.isAssignableFrom(clazz)) {
return clazz;
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
return classLoader.loadClass(className);
}
7. 总结
- 默认情况下,
Bootstrap
类中的类级字段catalinaLoader
,sharedLoader
,commonLoader
为同一个URLClassLoader
,而且该URLClassLoader
的Parent ClassLoader正是sun.misc.Launcher$AppClassLoader
。 - 加载
Server
等config/server.xml
中配置的组件的ClassLoader为catalinaLoader
字段指示的URLClassLoader。 也就是在Catalina的createStartDigester
方法中设置的digester.setUseContextClassLoader(true);
注意这里是遵从父优先的。 - Host,Engine的
parentClassLoader
字段,其赋值是通过digester组件来完成的,其值为加载Catalina
的ClassLoader,即sun.misc.Launcher$AppClassLoader
。 - 对Server,Context进行加载的ClassLoader为common.loader (即
java.net.URLClassLoader
), 而其parent Classloader为sun.misc.Launcher$AppClassLoader
(即:加载Catalina的类). Context使用InstanceManager来完成了实例化工作。 Servlet, Filter, Listener就是借助于它来完成实例化操作的,也是使用它来对Servlet, Filter, Listener的相关Class进行加载的。
- common.loader指代的URLClassLoader加载”org.apache.catalina” package下的类,以及ContainerServlet的实现类。
- 剩下的都使用classLoader来进行加载 (这个classLoader应该就是WebappClassLoader )。 这也是我们开发过程中最常接触到的ClassLoader,我们自定义的类都是由WebappClassLoader来加载的,注意该
WebappClassLoader
的parent ClassLoader由StandardContext.getParentClassLoader
来决定。
实际的加载类的工作最终是交给了WebappClassLoader了,大部分工作逻辑被基类WebappClassLoaderBase来完成, 而Loader接口基本只是做了外围的配置工作.
8. Links
- 《深入分析Java Web技术内幕(修订版)》 P168
- 《How Tomcat Works》 P157
- Tomcat类加载器以及应用间class隔离与共享