聲明:源碼版本為Tomcat 6.0.35
在本系列的第二篇文章中,曾經介紹過在Tomcat啟動時會初始化類加載器(ClassLoader),來處理整個Web工程中Class的加載問題。
類加載機制是Java平台中相當重要的核心技術,待筆者有所積累后會再次討論這個話題。在一般的業務開發中我們可能較少接觸和使用ClassLoader,但是在進行框架級程序開發時,設計良好的類加載機制能夠實現更好地模塊划分和更優的設計,如Java模塊化技術OSGi就是通過為每個組件聲明獨立的類加載器來實現組件的動態部署功能。在Tomcat的代碼實現中,為了優化內存空間以及不同應用間的類隔離,Tomcat通過內置的一些類加載器來完成了這些功能。
在Java語言中,ClassLoader是以父子關系存在的,Java本身也有一定的類加載規范。在Tomcat中基本的ClassLoader層級關系如下圖所示:

在Tomcat啟動的時候,會初始化圖示所示的類加載器。而上面的三個類加載器:CommonClassLoader、CatalinaClassLoader和SharedClassLoader是與具體部署的Web應用無關的,而WebappClassLoader則對應Web應用,每個Web應用都會有獨立的類加載器,從而實現類的隔離。
我們首先來看Tomcat的初始化,在Bootstrap的init方法中,會調用initClassLoaders方法,該方法負責前圖中前三個類加載器的初始化:
private void initClassLoaders() { try { //初始化CommonClassLoader commonLoader = createClassLoader("common", null); if( commonLoader == null ) { commonLoader=this.getClass().getClassLoader(); } //初始化其它兩個類加載器 catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { log.error("Class loader creation threw exception", t); System.exit(1); } }
我們可以看到,此書初始化了三個類加載器,並且catalinaLoader和sharedLoader都以commonLoader作為父類加載器,在這個方法中,將核心的業務交給了createClassLoader方法來實現:
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { //讀取配置屬性,相關的配置屬性在catalina.properties文件中 String value = CatalinaProperties.getProperty(name + ".loader"); //如果沒有對應的配置,將不會創建新的類加載器,而是返回傳入的父類加載器 if ((value == null) || (value.equals(""))) return parent; //解析得到的配置文件,確定本ClassLoader要加載那些目錄下的資源和JAR包等 StringTokenizer tokenizer = new StringTokenizer(value, ","); while (tokenizer.hasMoreElements()) { String repository = tokenizer.nextToken(); //此處省略的代碼為將配置文件中的${catalina.base}、${catalina.home}等變量轉 //換為絕對路徑 //格式化得到的位置路徑和類型 String[] locations = (String[]) repositoryLocations.toArray(new String[0]); Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]); //生成真正的類加載器 ClassLoader classLoader = ClassLoaderFactory.createClassLoader (locations, types, parent); //以下的代碼為將生成的類加載器注冊為MBean return classLoader; }
而每個類加載器所加載的路徑或JAR是在catalina.properties文件中定義的,默認的配置如下:
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
按照默認的配置,catalinaLoader和sharedLoader的配置項為空,因此不會創建對應的ClassLoader,而只會創建CommonClassLoader,該類加載器對應的Java實現類為:org.apache.catalina.loader. StandardClassLoader,該類繼承自org.apache.catalina.loader. URLClassLoader,有關Tomcat基礎類都會有該類加載器加載。例如在Bootstrap的init方法中,會調用Catalina類的init方法來完成相關操作:
public void init() throws Exception{ //將當前線程的類加載器設置為catalinaLoader Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); //使用catalinaLoader來加載Catalina類 Class startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); //調用Catalina的setParentClassLoader方法,設置為sharedLoader String methodName = "setParentClassLoader"; Class paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
以上為基礎的三個類加載器的初始化過程。在每個Web應用初始化的時候,StandardContext對象代表每個Web應用,它會使用WebappLoader類來加載Web應用,而WebappLoader中會初始化org.apache.catalina.loader. WebappClassLoader來為每個Web應用創建單獨的類加載器,在上一篇文章中,我們介紹過,當處理請求時,容器會根據請求的地址解析出由哪個Web應用來進行對應的處理,進而將當前線程的類加載器設置為請求Web應用的類加載器。讓我們看一下WebappClassLoader的核心方法,也就是loadClass:
public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class clazz = null; //首先檢查已加載的類 // (0) Check our previously loaded local class cache clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) Check our previously loaded class cache clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding J2SE classes try { clazz = system.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } // (0.5) Permission to access this class when using a SecurityManager if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; log.info(error, se); throw new ClassNotFoundException(error, se); } } } boolean delegateLoad = delegate || filter(name); //Tomcat允許按照配置來確定優先使用本Web應用的類加載器加載還是使用父類 //加載器來進行類加載,此處先使用父類加載器進行加載 // (1) Delegate to our parent if requested if (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } //使用本地的類加載器進行加載 // (2) Search local repositories if (log.isDebugEnabled()) log.debug(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } //如果沒有特殊配置的話,使用父類加載器加載類 // (3) Delegate to parent unconditionally if (!delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader at end: " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } //若最終類還是沒有找到,拋出異常 throw new ClassNotFoundException(name); }
以上就是Web應用中類加載的機制。在默認情況下,WebappClassLoader的父類加載器就是CommonClassLoader,但是我們可以通過修改catalina.properties文件來設置SharedClassLoader,從而實現多個Web應用共用類庫的效果。
