1.StandardClassLoader在Bootstrap类的initClassLoaders方法中创建: a.Bootstrap调用ClassLoaderFactory的createClassLoader(),创建StandardClassLoader对象 b.若没有指定StandardClassLoader的父ClassLoader,则:默认设置getSystemClassLoader()返回的ClassLoader作为其父类,而getSystemClassLoader()返回的ClassLoader通常就是:AppClassLoader(由上图可看出) 2.若StandardClassLoader创建成功,将作为Tomcat的根ClassLoader; 3.接下来,使用StandardClassLoader加载org.apache.catalina.startup.Catalina类并创建对象; 4.最终,也将StandardClassLoader设置到Catalina的parentClassLoader属性中;
至此,Tomcat容器的加载ClassLoader都将是StandardClassLoader;
#重点来了,我们主要关注的是,Tomcat如何加载我们的应用的!
首先,一个应用在Tomcat中由一个StandardContext表示,由StandardContext来解释web.xml配置文件中实例化所有的Servlet。另外,Servlet的class是由来指定的,所以可想而知,每个servlet类肯定是显示加载到Tomcat容器中的。
那么Servlet是如何被加载的呢?
StandardContext类的startInternal()方法,在StandardContext初始化时将会检查loader属性是否存在,不存在即会创建它,看如下代码:
if(getLoader() == null){ WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); }这里创建了WebappLoader 对象,而WebappLoader将创建WebappClassLoader作为其ClassLoader。
接着,翻阅StandardWrapper类的loadServlet()方法,可以发现,所有的Servlet都是InstanceManager实例化的,再看InstanceManager的构造函数:
public DefaultInstanceManager(Context context, Map<String,Map<String,String>> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader){ classLoader = catalinaContext.getLoader().getClassLoader(); privileged = catalinaContext.getPrivileged(); this.containerClassLoader = containerClassLoader; ... }这里,InstanceManager对象的Classloader也获取StandardContext的Loader中的ClassLoader,也就是前面设置的WebappClassLoader,所以Servlet的ClassLoader是WebappClassLoader。
着重说明WebappClassLoader的加载机制步骤: WebappClassLoader不像StandardClassLoader那么简单,因为它覆盖了父类的loadClass方法,使用自己的加载机制,这个加载机制有点复杂,大致分为以下几个步骤:
(1)首先检查在WebappClassLoader中是否已经加载过了,如果请求的类以前是被WebappClassLoader加载的,那么肯定在WebappClassLoader的缓存容器resourceEntries中; (2)如果不在WebappClassLoader的缓存容器resourceEntries中,则继续检查JVM虚拟机中是否已经加载过,即调用ClassLoader的findLoadedClass方法; (3)如果前2步的缓存中都没有,则先调用SystemClassLoader来加载请求的类,SystemClassLoader在这里是AppClassLoader,它将在当前的JVM的ClassPath路径下查找请求的类; (4)检查请求的类是否在packageTriggers定义的包名下,如果在这个设置的包目录下,则将通过StandardClassLoader类来加载; (5)如果仍然没有找到,将由WebappClassLoader来加载,它将在这个应用的WEB-INF/classes目录下查找请求的类文件的字节码。找到后,创建一个ResourceEntry对象保存这类的元信息,并把它保存在WebappClassLoader的resourceEntries容器中,以便下次查找。 (6)接着,调用defineClass方法生成请求类的Class对象并返回给InstanceManager来创建实例。
从上面的过程来看,Tomcat仍然沿用了JVM的类加载规范,即委托式加载,保证了核心类通过AppClassLoader来加载。但是,Tomcat会优先检查WebappClassLoader已经加载的缓存,而不是JVM的findLoadedClass缓存(从第1和第2步的顺序即可看出),这点要注意!
参考文献:《深入分析Java Web技术内幕(修订版)》许令波著 电子工业出版社