In order to place them centrally, during the process of collecting data, all the information was directly put on CSDN in the form of drafts. After collecting data for a week, I deleted all the information this morning because of the unskilled operation of CSDN. . . . . . . . . . . .
1 Overview
Although I started trying to read the source code of Tomcat more than a year ago, the source code of Tomcat is still relatively large, so the understanding of Tomcat has been relatively fragmented and has not formed a system. Recently, I encountered some problems caused by ClassLoader, so I decided to take the opportunity to study the application of ClassLoader in Tomcat. In this study, we will explain it according to the time line.
2. ClassLoader initialization
2.1 Bootstrap
Class
Bootstrap
As the entry point of Tomcat startup logic, there are two main parts of the code involved in ClassLoader, namely init
and initClassLoaders
methods:
Bootstrap.init
method
initClassLoaders(); // 重点关注这个
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
Bootstrap.initClassLoaders
Method
The initialized URLClassLoader here follows the ClassLoader's level delegation reception mechanism .
// 该方法负责给类级字段`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);
}
}
Let's take a look at the environment variables of Tomcat runtime through JVISUALVM
sun.misc.Launcher$AppClassLoader
Responsible for loading is the ClassSystem.getProerty(“java.class.path”)
under .- The common.loader is responsible for loading the Class indicated by ${common.loader}.
- So loading Server, Context is still the URLClassLoader generated by common.loader.
2.2 Catalina.createStartDigester
Methods
- What this method does is build a
conf/server.xml
Digester component that parses XML configuration files (by default). - Assign values to the relevant fields of the ClassLoader in the relevant container (Container):
Catalina.SetParentClassLoaderRule
. Responsible forEngine
settingparentClassLoader
(the field is defined in the container base classContainerBase
).- located
HostRuleSet
inCopyParentClassLoaderRule
. Set (Host
theparentClassLoader
field is defined in the container base classContainerBase
). - The above as the source of the
parentClassLoader
value is the class that loads Catalina --sun.misc.Launcher$AppClassLoader
. - For it overrides the method
Context
inherited from .ContainerBase
getParentClassLoader
- Note that there is such a relevant code in this method
digester.setUseContextClassLoader(true);
. Combining the above,Thread.currentThread().setContextClassLoader(catalinaLoader);
we can conclude that Classes such as Server and Context are loaded using the above catalinaLoader (URLClassLoader type, and the parent ClassLoader is the loadedCatalina
class—sun.misc.Launcher$AppClassLoader
).
3. StandardContext
Class
- The focus is still on this.
It overrides methods inherited from the base
ContainerBase
classgetParentClassLoader
.@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()); }
Which involves three related classes of Tomcat itself
WebappLoader
Loader
WebappClassLoaderBase
ClassLoader
DefaultInstanceManager
InstanceManager
The first thing we will look at is the startInternal
method
//
// 代码实在太多了,故进行了非必要的删减
//
// ------------------------------------ 初始化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);
}
Now let's take a look at WebappClassLoaderBase, WebappLoader, InstanceManager in detail
4. WebappLoader
Class
Loader
The definition of the interface shows that it is not responsible for the actual class loading.Lifecycle
The interfacestartInternal
is implemented , and it is also instantiated and configuredWebappClassLoader
in the method .Loader
The main function of is to perform related configuration; the logic for loading Class is stillWebappClassLoader
in .
5. WebappClassLoader
Class
- Most of the logic resides in its base class
WebappClassLoaderBase
. - The main concern is to override the inheritance from the base class
loadClass
,findClass
(you can read "In-depth Analysis of Java Web Technology (Revised Edition)" P157). loadClass
The amount of code in thefindClass
method is relatively large, and interested readers can study it by themselves. related to
- Customize various rules when loading classes.
- Cache previously loaded classes.
- Preload certain classes.
WebappClassLoaderBase
There are some fields in the base class that are worth paying attention to
. 1. triggers
The Servlet specification requires that related classes are not allowed to be loaded by the servlet container. Here is the relevant implementation. And the sentence we often see in the console: validateJarFile(xxx) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: xxx
, is located in its validateJarFile
method - line 3373
2. packageTriggers
Before delegating to the parent classloader, it is not allowed to load itself first, a collection of classes.
3. wait
6. InstanceManager
Class
- The implementation class is
DefaultInstanceManager
. The assignment of several key fields
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;
It is this
InstanceManager
class that is responsible for loading all ,Servlet
,Filter
,Listener
etc.StandardContext.listenerStart()
. Here you can findStandardContext
theInstanceManager
fields used to be responsible for instantiationListener
.StandardContext.filterStart()
. Here we can finally traceApplicationFilterConfig
the constructor of the class, which is also the usedStandardContext
fieldInstanceManager
to be responsible for instantiationFilter
.loadOnStartup(findChildren())
.StandardWrapper
The method that can be traced hereloadServlet
, it isStandardContext
theInstanceManager
field that is used to be responsible for instantiationServlet
.
The logic responsible for loading Servlet, Filter and Listener in this class is its internal loadClass
method ,
// ----------------------- 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. Summary
- By default,
Bootstrap
the class-level field in a classcatalinaLoader
,sharedLoader
,commonLoader
is the sameURLClassLoader
, and theURLClassLoader
Parent ClassLoader is exactly thatsun.misc.Launcher$AppClassLoader
. - The ClassLoader of the component configured in loading
Server
etc. is the URLClassLoader indicated by the field. That is, the attention set in Catalina's method is here to obey the parent's priority .config/server.xml
catalinaLoader
createStartDigester
digester.setUseContextClassLoader(true);
- Host, the field of Engine
parentClassLoader
, its assignment is done through the digester component, and its value is the loadedCatalina
ClassLoader, iesun.misc.Launcher$AppClassLoader
. - The ClassLoader that loads Server and Context is common.loader (ie
java.net.URLClassLoader
), and its parent Classloader issun.misc.Launcher$AppClassLoader
(ie: the class that loads Catalina). Context uses InstanceManager to complete the instantiation work. Servlet, Filter, Listener use it to complete the instantiation operation, and also use it to load the related classes of Servlet, Filter, and Listener.
- The URLClassLoader referred to by common.loader loads the classes under the "org.apache.catalina" package, as well as the implementation class of ContainerServlet.
- The rest are loaded using classLoader (this classLoader should be WebappClassLoader). This is also the ClassLoader that we most often come into contact with in the development process. Our custom classes are loaded by the WebappClassLoader. Pay attention to the origin
WebappClassLoader
of the parent ClassLoaderStandardContext.getParentClassLoader
.
The actual work of loading classes is finally handed over to WebappClassLoader, most of the work logic is completed by the base class WebappClassLoaderBase, and the Loader interface basically only does the peripheral configuration work.
8. Links
- "In-depth Analysis of Java Web Technology Insider (Revised Edition)" P168
- 《How Tomcat Works》 P157
- Tomcat class loader and class isolation and sharing between applications