本文接下來分析tomcat的類載入器,tomcat需要實現一個自定義的載入器,而不能使用系統類載入器
(1)限制serlvet訪問當前運行的java虛擬機中環境變量CLASSPATH指明的路徑下的所有類和庫,而只允許載入WEB-INF/class目錄及其子目錄下的類,和從部署的庫到WEB-INF/lib目錄載入類
(2)提供自動重載的功能,即當WEB-INF/class目錄或WEB-INF/lib目錄下的類發生變化時,Web應用程序會重新載入這些類
我們先來回顧一下java的類載入器,當我們創建java類的實例時,都必須先將類載入到內存中,java虛擬機使用類載入器來載入需要的類
JVM使用三種類型的類載入器來載入所需要的類,分別為引導類載入器(bootstrap class loader)、擴展類載入器(extensions class loader)和系統類載入器(system class loader)
- 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,并不繼承自 java.lang.ClassLoader。
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
- 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。
JVM采用一種代理模型的類載入機制,可以解決類載入過程中的安全性問題;每當系統需要載入一個類的時候,會首先調用系統類載入器,而系統類載入器將載入類的任務委派給其父載入器,即擴展類載入器,同樣擴展類載入器將載入類的任務委派給其父載入器,即引導類載入器;因此引導類載入器會首先執行載入某個類的任務,如果引導類載入器找不到需要載入的類,那么擴展類載入器嘗試載入該類,如果擴展類載入器也找不到該類,就輪到系統類載入器繼續執行載入任務。如果系統類載入器還是找不到該類,則會拋出java.lang.ClassNotFoundException異常
Tomcat中的載入器是指Web應用程序載入器,而不僅僅指類載入器,載入器必須實現org.apache.catalina.Loader接口
public interface Loader { public ClassLoader getClassLoader(); public Container getContainer(); public void setContainer(Container container); public DefaultContext getDefaultContext(); public void setDefaultContext(DefaultContext defaultContext); public boolean getDelegate(); public void setDelegate( boolean delegate); public String getInfo(); public boolean getReloadable(); public void setReloadable( boolean reloadable); public void addPropertyChangeListener(PropertyChangeListener listener); public void addRepository(String repository); public String[] findRepositories(); public boolean modified(); public void removePropertyChangeListener(PropertyChangeListener listener); }
下面我們來具體來分析tomcat容器中 Web應用程序載入器的具體實現,即org.apache.catalina.loader.WebappLoader類實現了上面的Loader接口,負責載入Web應用程序中所使用到的類。
WebappLoader類會創建org.apache.catalina.loader.WebappClassLoader類的實例作為其類載入器;
同時WebappLoader類也實現了org.apache.catalina.Lifecycle接口,可以由其相關聯的容器來啟動或關閉;
此外,WebappLoader類還實現了java.lang.Runnable接口,通過一個線程不斷地調用其類載入器的modified()方法。如果modified()方法返回true, WebappLoader的實例會通知其關聯的servlet容器(在這里是Context類的實例),然后由Context實例來完成類的重新載入。
當調用WebappLoader類的start()方法時,會完成以下幾項重要工作:
(1)創建一個類載入器
(2)設置倉庫
(3)設置類路徑
(4)設置訪問權限
(5)啟動一個新線程來支持自動重載
WebappLoader類會調用其私有方法createClassLoader()方法來創建默認的類載入器
/** * Create associated classLoader. */ private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null ; if (parentClassLoader == null ) { // Will cause a ClassCast is the class does not extend WCL, but // this is on purpose (the exception will be caught and rethrown) classLoader = (WebappClassLoader) clazz.newInstance(); } else { Class[] argTypes = { ClassLoader. class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); } return classLoader; }
String loaderClass 的默認值為org.apache.catalina.loader.WebappClassLoader
啟動方法后面的設置倉庫、設置類路徑、設置訪問權限等都與類載入器的初始化相關
WebappLoader類支持自動重載功能,如果WEB-INF/class目錄或WEB-INF/lib目錄下的某些類被重新編譯了,那么這些類會自動重新載入,而無需重啟tomcat。WebappLoader類使用一個線程周期性地檢查每個資源的時間戳,我們可以調用setCheckInterval()方法設置間隔時間
/** * The background thread that checks for session timeouts and shutdown. */ public void run() { if (debug >= 1 ) log( "BACKGROUND THREAD Starting" ); // Loop until the termination semaphore is set while (! threadDone) { // Wait for our check interval threadSleep(); if (! started) break ; try { // Perform our modification check if (! classLoader.modified()) continue ; } catch (Exception e) { log(sm.getString( "webappLoader.failModifiedCheck" ), e); continue ; } // Handle a need for reloading notifyContext(); break ; } if (debug >= 1 ) log( "BACKGROUND THREAD Stopping" ); }
在上面run()方法里面的循環中,首先使線程休眠一段時間,然后調用 WebappLoader實例的類載入器的 modified()方法檢查已經載入的類是否被修改,肉沒有修改則重新執行循環;否則調用私有方法notifyContext(),通知與WebappLoader實例相關聯的Context容器重新載入相關類
/** * Notify our Context that a reload is appropriate. */ private void notifyContext() { WebappContextNotifier notifier = new WebappContextNotifier(); ( new Thread(notifier)).start(); }
上面方法中的WebappContextNotifier為內部類,實現了Runnable接口
/** * Private thread class to notify our associated Context that we have * recognized the need for a reload. */ protected class WebappContextNotifier implements Runnable { /** * Perform the requested notification. */ public void run() { ((Context) container).reload(); } }
下面我們來分析web應用程序中負責載入類的類載入器org.apache.catalina.loader.WebappClassLoader,該類繼承自java.net.URLClassLoader,同時實現了org.apache.catalina.loader.Reloader接口
public interface Reloader { public void addRepository(String repository); public String[] findRepositories(); public boolean modified(); }
該接口用來與倉庫操作相關,同時檢查web應用程序中的某個servlet或相關的類是否被修改
每個由WebappClassLoader載入的類,都視為資源,由org.apache.catalina.loader.ResourceEntry類的實例表示,存儲了類的相關信息
public class ResourceEntry { public long lastModified = -1 ; public byte [] binaryContent = null ; public Class loadedClass = null ; public URL source = null ; public URL codeBase = null ; public Manifest manifest = null ; public Certificate[] certificates = null ; }
在WebappClassLoader類中,所有已經緩存的類存儲在名為resourceEntries的HashMap類型的變量中,而載入失敗的類被存儲在另一個名為notFoundResources的HashMap類型的變量中
下面是WebappClassLoader的loadClass()方法的具體實現
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (debug >= 2 ) log( "loadClass(" + name + ", " + resolve + ")" ); Class clazz = null ; // Don't load classes if class loader is stopped if (! started) { log( "Lifecycle error : CL stopped" ); throw new ClassNotFoundException(name); } // (0) Check our previously loaded local class cache clazz = findLoadedClass0(name); if (clazz != null ) { if (debug >= 3 ) log( " 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 (debug >= 3 ) log( " 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; System.out.println(error); se.printStackTrace(); log(error); throw new ClassNotFoundException(error); } } } boolean delegateLoad = delegate || filter(name); // (1) Delegate to our parent if requested if (delegateLoad) { if (debug >= 3 ) log( " Delegating to parent classloader" ); ClassLoader loader = parent; if (loader == null ) loader = system; try { clazz = loader.loadClass(name); if (clazz != null ) { if (debug >= 3 ) log( " Loading class from parent" ); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // (2) Search local repositories if (debug >= 3 ) log( " Searching local repositories" ); try { clazz = findClass(name); if (clazz != null ) { if (debug >= 3 ) log( " Loading class from local repository" ); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } // (3) Delegate to parent unconditionally if (! delegateLoad) { if (debug >= 3 ) log( " Delegating to parent classloader" ); ClassLoader loader = parent; if (loader == null ) loader = system; try { clazz = loader.loadClass(name); if (clazz != null ) { if (debug >= 3 ) log( " Loading class from parent" ); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // This class was not found throw new ClassNotFoundException(name); }
在WebappClassLoader實例載入類時,首先是檢查緩存,然后再載入指定類(如果設置了安全管理。在代理載入器載入前還要檢測類型的安全)
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
