|
All the functionality of ClassLoader is nested in org.apache.catalina.loader.WebappClassLoader
1) Structure of WebappClassLoader
public class WebappClassLoader
extends URLClassLoader
implements Lifecycle{
/**
* The cache of ResourceEntry for classes and resources we have loaded,
* keyed by resource name.
*/
protected HashMap resourceEntries = new HashMap();
/**
* The list of not found resources.
*/
protected HashMap notFoundResources = new HashMap();
public Class loadClass(String name, boolean resolve);
protected Class findLoadedClass0(String name);
public Class findClass(String name);
protected Class findClassInternal(String name);
// This method is derived from java.lang.ClassLoader; it will invoke native method searching JVM for classes loaded by current class loaders
// It will return the loaded class if and only if the class is previously loaded(call defineClass method) by current class loader instance
protected final Class<?> findLoadedClass(String name);
// This method is derived from java.lang.ClassLoader, it will invoke native method and it is the only way of converting byte[] into Class<?>; even if in our self defined ClassLoader.
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain);
}
2) Overrides loadClass() on URLClassLoader
public Class loadClass(String name, boolean resolve){
// (0) Check local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
return (clazz);
}
// (0.1) Check ClassLoader inherent cache, call native method
clazz = findLoadedClass(name);
if (clazz != null) {
return (clazz);
}
// Here we can guarantee that target class hasn't been loaded by WebAppClassLoader before, but whether it has been loaded by System/Ext/Bootstrap ClassLoader is unknown
// Try to find class from bootstrap/ext/system ClassLoader's inherent cache, if cannot find, try to load .class from class path; throw exception when cannot be load either
try {
clazz = system.loadClass(name);
return (clazz);
} catch (ClassNotFoundException e) {
// Ignore
}
// Here we are sure that the target class hasn't loaded by current application before.
// We will try to load it from container's WEB-INF/libs
// delegateLoad means WebappClassLoader will use parent first model, if false, means WebappClassLoader will use child first model.
boolean delegateLoad = delegate || filter(name);
// load class with parent
if (delegateLoad) {
try{
clazz = parent.loadClass(name);
if (clazz != null) {
return (clazz);
}
}catch(ClassNotFoundException e){
// swallow e
}
}
// load class with child(It is the end of parent delegation, and it is the start of child first)
try {
clazz = findClass(name);
if (clazz != null) {
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
// load class with parent(It is then end of child first model)
if (!delegateLoad) {
ClassLoader loader = parent;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
// All possible tries has failed, throw exception
throw new ClassNotFoundException(name);
}
// get class from WebappClassLoader customized class cache
protected Class findLoadedClass0(String name) {
ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
if (entry != null) {
return entry.loadedClass;
}
return (null); // FIXME - findLoadedResource()
}
3) How tomcat class reload works? Take a look at WebappClassLoader.stop()/start() as it implements interface Lifecycle;
// All ClassLoader did is clear its local class cache because we do not have the access to inherited java.lang.ClassLoader's inherent cache.
// reload is realized by calling stop() and start(), then loadClass() loading all classes under WEB-INF/lib
// loadClass(name) will first check local class cache which will return null, then it will check inherent cache, that would return "null" because
// every time we restart, our WebappClassLoader is newly instantiated(the old instance is discarded)
public void stop(){
started = false;
notFoundResources.clear();
resourceEntries.clear();
repositoryURLs = null;
}
public void start() throws LifecycleException {
started = true;
}
The logic where a new WebappClassLoader instance is created every time the container is restarted:
// WebappLoader
public void start(){
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDebug(this.debug);
classLoader.setDelegate(this.delegate);
// ...
if (classLoader instanceof Lifecycle)
((Lifecycle) classLoader).start();
}
/**
* Create associated classLoader.
*/
private WebappClassLoader createClassLoader()
throws Exception {
Class clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = Thread.currentThread().getContextClassLoader();
}
Class[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
return classLoader;
}
Summary:
1) We do not have direct access to native class cache defined by JVM. But we can make a little tweaks to realize class loading:
1> We can suppose the native class cache keeps a map of Map<String, Class> with key of ClassLoader.instance.hashCode[classname+instancecode], and value of loaded class.
2> The class would be recorded into native cache when the class loader's instance calls defineClass(), and the cached class would be returned only when current class loader instance calls findLoadedClass0(); |
|