tomcat(8)载入器
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                tomcat(8)载入器
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
                                
                            
                            
                            【0】README
 
0.0)本文部分描述轉自“深入剖析tomcat”,旨在學習?tomcat(8)載入器 的基礎知識;
 
0.1)一個標準web 應用程序中的載入器:簡單來說就是 tomcat中的載入器;
 
0.2)servlet容器需要實現一個自定義的載入器,而不能簡單地使用系統的類載入器的原因:(干貨——為什么servlet容器要實現一個自定義的載入器)
 
 0.2.1)原因1:因為servlet容器不應該完全信任它正在運行的servlet類; 0.2.2)原因2:如果使用系統類的載入器載入某個servlet類所使用的全部類,那么servlet就能夠訪問所有的類,包括當前運行的java 虛擬機中環境變量CLASSPATH指明的路徑下的所有的類和庫,這是非常危險的;因為 servlet應該只允許載入 WEB-INF/classes目錄及其子目錄下的類,和部署到 WEB-INF/lib 目錄下的類(類庫); 0.2.3)原因3:為了提供自動重載的功能,即當 WEB-INF/classes 目錄或 WEB-INF/lib目錄下的類發生變化時,web 應用程序會重新載入這些類。在tomcat的載入器的實現中, 類載入器使用一個額外的線程來不斷檢查servlet 類和其它類的文件的時間戳。 
 
0.3)在Catalina中: 載入器是 org.apache.catalina.Loader接口的實例; 若要支持自動重載功能, 則載入器必須實現 org.apache.catalina.loader.Reloader 接口;
 
0.4)intro to 兩個術語
 
0.4.1)倉庫(repository):倉庫表示類載入器會在哪里搜索要載入的類;
 
0.4.2)資源(resource):而資源指的是一個類載入器中的 DirContext對象,它的文件根路徑指的就是 上下文的文件根路徑;
 
0.5)for complete source code, please visit?https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter8/chapter8
 
【1】?java的類載入器 0)intro to 類載入器: 每次創建java類的實例時,都必須先將類載入到內存中, java虛擬機使用類載入器來載入需要的類。一般case下, 類載入器會在一些java 核心類庫,以及環境變量 classpath 中指明的目錄中 搜索相關類。如果在這些位置都找不到要載入的類,就會拋出 java.lang.ClassNotFoundException 異常; 1)從J2SE 1.2 開始, jvm 使用了3種類載入器來載入所需要的類:分別是引導類載入器(bootstrap class loader), 擴展類載入器(extension?class loader) 和 系統類載入器(system?class loader)。而 引導類載入器是 擴展類載入器的父親,?擴展類載入器是 系統類載入器的父親。(干貨——jvm 使用了3種類載入器來載入所需要的類)
2)3種載入器的詳細描述:(干貨——3種載入器的 spec intro) 2.1)引導類載入器: 用于引導啟動 jvm。當調用 javax.exe 是, 就會啟動引導類載入器。引導類載入器是使用本地代碼來實現的, 因為它用來載入運行 jvm 所需要的類, 以及所有的 java 核心類。如 java.lang 包 和 java.io 包下的類。啟動類載入器會在 rt.jar 和 i18n.jar 等java 包中搜索要載入的類。 2.2) 擴展類載入器: 負責載入標準擴展目錄中的類。sum 公司的 jvm 的標準擴展目錄是 /jdk/jre/lib/ext/; 2.3)系統類載入器:是默認的類載入器, 他會搜索在環境變量 CLASSPATH 中指明的路徑和 JAR 文件; 3)jvm 使用的是哪種類載入器呢? 3.1)答案在于 類載入器的代理模型。 3.2)載入一個類 的steps(每當需要載入一個類 的時候):(干貨中的干貨——載入一個類 的steps) step1)首先調用 系統類載入器,但并不會立即載入這個類; step2)相反,他會將載入類的任務交給其父類載入器——擴展類載入器; step3)而擴展類載入器也會將載入任務交給其父類載入器——引導類載入器; 3.3)因此,引導類載入器會首先執行載入某個類的任務。接下來有3種cases: case1)如果引導類載入器找不到需要載入的類,那么擴展類載入器會嘗試 載入該類; case2)如果擴展類載入器也找不到該類,就輪到系統類載入器繼續執行載入任務; case3)如果系統類載入器也找不到這個類,拋出 java.lang.ClassNotFoundException 異常;
3.4)為什么要這么做? 代理模型的重要用途就是為了 解決 類載入過程中的安全問題;(干貨——代理模型的重要用途) 3.5)看個荔枝: 當程序的某個地方調用了 自定義的 java.lang.Object 類時, 系統類載入器會將載入工作 委托給 擴展類載入器,繼而會被交給 引導類載入器。 引導類載入器搜索其 核心庫, 找到標準的 java.lang.Object 類, 并將之實例化。 結果是, 自定義的 java.lang.Object 類并沒有被載入(這正是我們想要的)。
4)關于 java 中類載入機制的一件重要事情是, 可以通過繼承抽象類 java.lang.ClassLoader 類 編寫自己的類載入器。而 tomcat 要使用自定義類載入器的原因有3條(reasons):(干貨——tomcat 要使用自定義類載入器的原因) r1)為了在載入類中指定某些規則; r2)為了緩存已經載入的類; r3)為了實現類的預載入,方便使用;
【2】Loader接口 0)載入web 應用程序中需要的servlet類及其相關類需要遵循的一些rules:(干貨——web app中用到的servlet需要遵循的一些rules) r1)應用程序中的 servlet 只能引用部署在 WEB-INF/classes 目錄及其子目錄下的類; r2)但是,servlet類不能訪問其他路徑中的類,即使這些類包含在運行當前的Tomcat的 jvm 的 classpath 環境變量中; r3)此外, servlet類只能訪問 WEB-INF/lib 目錄下的庫,其他目錄中的類庫不能訪問; 1)Tomcat載入器指的是web 應用程序載入器,而不僅僅指 類載入器:(干貨——tomcat載入器不僅僅是類載入器) 1.1)載入器必須實現 org.apache.catalina.Loader接口; 1.2)載入器的實現中,會使用一個 自定義類載入器: 它是 org.apache.catalina.loader.WebappClassLoader類的一個實例;(Loader接口的getClassLoader() 方法來獲取) 2)Loader接口定義的對倉庫集合的操作:(Tomcat中的倉庫就是WEB-INFO/classes 目錄和 WEB-INF/lib 目錄) 2.1)intro to 倉庫:一個web app的倉庫指的是,其 WEB-INFO/classes 目錄和 WEB-INF/lib 目錄,這兩個目錄作為倉庫添加到 載入器中; 2.2)addRepository方法和 findRepositories() 方法:添加一個新倉庫和返回所有倉庫集合(數組對象); 3)Tomcat的載入器通常會與一個 Context級別的servlet容器相關聯; 4)自動重載:如果Context 容器中的一個或多個類被修改了, 載入器也可以支持對類的自動重載; 4.1)Loader接口使用modified() 方法來支持類的自動重載:如果倉庫中的一個或多個類被修改了,那么modified() 方法會返回true,才能提供自動重載的支持; 4.2)載入器類本身并不會自動重載: 它會調用 Context接口(容器)的reload() 方法來實現; 4.3)setReloadable() 和 getReloadable() 方法:用來指明是否支持載入器的自動重載; 4.4)自動重載的default case:默認情況下是 禁用了自動重載的功能的,要想啟動Context容器的自動重載功能,需要再 server.xml 文件中添加一個 Context元素,如下所示:(干貨——默認case下,Context容器的自動重載功能是closed,通過如下方式啟用自動重載功能) <Context path="/myApp" docBase="myApp" debug="0" reloadable="true" /> 4.5)載入器的實現會指明是否要委托給一個父類載入器;(Loader接口中聲明了 getDelegate()方法 和 setDelegate()方法) 4.6)org.apache.catalina.Loader的聲明代碼如下: package org.apache.catalina; import java.beans.PropertyChangeListener;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); } 5)Catalina提供了 org.apache.catalina.loader.WebappLoader類作為 Loader接口的實現:?WebappLoader 對象中使用 org.apache.catalina.loader.WebappClassLoader 類的實例作為其類載入器,該類繼承自 java.net.URLClassLoader類;(干貨——WebappClassLoader類很重要,已經提及過兩次了) 6)Loader接口及其實現類的URL類圖如下:
Attention)當與某個載入器相關聯的容器(如 Context)需要使用某個 servlet類時,即當該類的某個方法被調用時, 容器會先調用載入器的 getClassLoader() 方法來獲取類載入器的實例。然后,容器會調用類 載入器的 loadClass() 方法來載入這個servlet類;
【3】Reloader接口 1)intro to Reloader接口:為了支持類的自動重載功能,類載入器實現需要實現 org.apache.catalina.loader.Reloader接口; 2)最重要的方法:modified方法,其作用是,如果web app 中的某個servlet 或相關類被修改了,modified方法會返回true;(干貨——Reloader接口的最重要的方法modified方法)
【4】 WebappLoader類(web 應用程序載入器, 負責載入web 應用程序中所使用到的類) 1)如上述的UML類圖所示,WebappLoader類實現 Runnable接口,當調用WebappLoader類的start方法時,會完成以下幾項重要工作(works):
w1)創建一個類載入器; w2)設置倉庫; w3)設置類路徑; w4)設置訪問權限; w5)啟動一個新線程來支持自動重載; (下面的內容將分別對以上works 進行 intro) public void start() throws LifecycleException {// org.apache.catalina.loader.WebappLoader.start(),為了看其 outline,我沒有對該method做刪減// Validate and update our current component stateif (started)throw new LifecycleException(sm.getString("webappLoader.alreadyStarted"));if (debug >= 1)log(sm.getString("webappLoader.starting"));lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;if (container.getResources() == null)return;// Register a stream handler factory for the JNDI protocolURLStreamHandlerFactory streamHandlerFactory =new DirContextURLStreamHandlerFactory();try {URL.setURLStreamHandlerFactory(streamHandlerFactory);} catch (Throwable t) {// Ignore the error here.}// Construct a class loader based on our current repositories listtry {classLoader = createClassLoader(); // 創建一個類載入器,下面是設置類載入器classLoader.setResources(container.getResources()); classLoader.setDebug(this.debug);classLoader.setDelegate(this.delegate);for (int i = 0; i < repositories.length; i++) {classLoader.addRepository(repositories[i]);}// Configure our repositoriessetRepositories(); // 設置倉庫setClassPath(); // 設置類路徑setPermissions(); // 設置訪問權限if (classLoader instanceof Lifecycle)((Lifecycle) classLoader).start(); // Binding the Webapp class loader to the directory contextDirContextURLStreamHandler.bind((ClassLoader) classLoader, this.container.getResources());} catch (Throwable t) {throw new LifecycleException("start: ", t);}// Validate that all required packages are actually availablevalidatePackages();// Start our background thread if we are reloadableif (reloadable) {log(sm.getString("webappLoader.reloading"));try {threadStart(); // 啟動一個新線程來支持自動重載} catch (IllegalStateException e) {throw new LifecycleException(e);}}}
【4.1】創建類載入器 1)WebappLoader類提供了 getLoaderClass() 方法 和 setLoaderClass() 方法來獲取或改變 其私有變量的 loaderClass 的值; 1.1)該私有變量保存了一個字符串類型的值,指明了類載入器所要載入的類的名字;(干貨——私有變量loaderClass指明了類載入器所要載入的類的名字) 1.2)默認情況下: 變量loadClass的值是 org.apache.catalina.loader.WebappClassLoader ; 1.3)也可以通過繼承 WebappClassLoader 類的方式實現自己的類載入器,然后調用 setLoaderClass() 方法強制WebappLoader實例使用 自定義類載入器。否則的話,在它啟動時,WebappLoader 類會調用其 私有方法 createClassLoader() 方法來創建 默認 的 ?類載入器; Attention) A1)可以不使用 WebappClassLoader 類的實例,而使用其他類的實例作為類載入器; A2)createClassLoader()方法的返回值是: WebappLoader類型的, 因此,如果自定義類型沒有繼承自 WebappClassLoader l類,createClassLoader方法就會拋出一個異常; private WebappClassLoader createClassLoader()throws Exception { // org.apache.catalina.loader.WebappLoader.createClassLoader()Class clazz = Class.forName(loaderClass);WebappClassLoader classLoader = null; // highlight line.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;} 【4.2】設置倉庫 private void setRepositories() { // org.apache.catalina.loader.WebappLoader.setRepositories()if (!(container instanceof Context))return;ServletContext servletContext =((Context) container).getServletContext();if (servletContext == null)return;// Loading the work directoryFile workDir =(File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);if (workDir == null)return;log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));DirContext resources = container.getResources();// Setting up the class repository (/WEB-INF/classes), if it existsString classesPath = "/WEB-INF/classes";DirContext classes = null;try {Object object = resources.lookup(classesPath);if (object instanceof DirContext) {classes = (DirContext) object;}} catch(NamingException e) {// Silent catch: it's valid that no /WEB-INF/classes collection// exists}if (classes != null) {File classRepository = null;String absoluteClassesPath =servletContext.getRealPath(classesPath);if (absoluteClassesPath != null) {classRepository = new File(absoluteClassesPath);} else {classRepository = new File(workDir, classesPath);classRepository.mkdirs();copyDir(classes, classRepository);}log(sm.getString("webappLoader.classDeploy", classesPath,classRepository.getAbsolutePath()));// Adding the repository to the class loaderclassLoader.addRepository(classesPath + "/", classRepository);}// Setting up the JAR repository (/WEB-INF/lib), if it existsString libPath = "/WEB-INF/lib";classLoader.setJarPath(libPath);DirContext libDir = null;// Looking up directory /WEB-INF/lib in the contexttry {Object object = resources.lookup(libPath);if (object instanceof DirContext)libDir = (DirContext) object;} catch(NamingException e) {// Silent catch: it's valid that no /WEB-INF/lib collection// exists}if (libDir != null) {boolean copyJars = false;String absoluteLibPath = servletContext.getRealPath(libPath);File destDir = null;if (absoluteLibPath != null) {destDir = new File(absoluteLibPath);} else {copyJars = true;destDir = new File(workDir, libPath);destDir.mkdirs();}// Looking up directory /WEB-INF/lib in the contexttry {NamingEnumeration enum = resources.listBindings(libPath);while (enum.hasMoreElements()) {Binding binding = (Binding) enum.nextElement();String filename = libPath + "/" + binding.getName();if (!filename.endsWith(".jar"))continue;// Copy JAR in the work directory, always (the JAR file// would get locked otherwise, which would make it// impossible to update it or remove it at runtime)File destFile = new File(destDir, binding.getName());log(sm.getString("webappLoader.jarDeploy", filename,destFile.getAbsolutePath()));Resource jarResource = (Resource) binding.getObject();if (copyJars) {if (!copy(jarResource.streamContent(),new FileOutputStream(destFile)))continue;}JarFile jarFile = new JarFile(destFile);classLoader.addJar(filename, jarFile, destFile);}} catch (NamingException e) {// Silent catch: it's valid that no /WEB-INF/lib directory// exists} catch (IOException e) {e.printStackTrace();} }} 【4.3】設置類路徑 1)設置類路徑的任務是:通過在 start()方法中調用 setClassPath方法完成的。該方法會在servlet上下文中 為 Jasper JSP 編譯器設置一個字符串形式的屬性來指明類路徑信息; private void setClassPath() { // org.apache.catalina.loader.WebappLoader.setClassPath()// Validate our current state informationif (!(container instanceof Context))return;ServletContext servletContext =((Context) container).getServletContext();if (servletContext == null)return;StringBuffer classpath = new StringBuffer();// Assemble the class path information from our class loader chainClassLoader loader = getClassLoader();int layers = 0;int n = 0;while ((layers < 3) && (loader != null)) {if (!(loader instanceof URLClassLoader))break;URL repositories[] =((URLClassLoader) loader).getURLs();for (int i = 0; i < repositories.length; i++) {String repository = repositories[i].toString();if (repository.startsWith("file://"))repository = repository.substring(7);else if (repository.startsWith("file:"))repository = repository.substring(5);else if (repository.startsWith("jndi:"))repository =servletContext.getRealPath(repository.substring(5));elsecontinue;if (repository == null)continue;if (n > 0)classpath.append(File.pathSeparator);classpath.append(repository);n++;}loader = loader.getParent();layers++;}// Store the assembled class path as a servlet context attributeservletContext.setAttribute(Globals.CLASS_PATH_ATTR,classpath.toString());} 【4.4】設置訪問權限 1)若運行Tomcat時,使用了安全管理器:則 setPermissions() 方法會為 類載入器設置訪問相關目錄的權限; private void setPermissions() {// org.apache.catalina.loader.WebappLoader.setPermission()if (System.getSecurityManager() == null)return;if (!(container instanceof Context))return;// Tell the class loader the root of the contextServletContext servletContext =((Context) container).getServletContext();// Assigning permissions for the work directoryFile workDir =(File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);if (workDir != null) {try {String workDirPath = workDir.getCanonicalPath();classLoader.addPermission(new FilePermission(workDirPath, "read,write"));classLoader.addPermission(new FilePermission(workDirPath + File.separator + "-", "read,write,delete"));} catch (IOException e) {// Ignore}} try {URL rootURL = servletContext.getResource("/");classLoader.addPermission(rootURL);String contextRoot = servletContext.getRealPath("/");if (contextRoot != null) {try {contextRoot = (new File(contextRoot)).getCanonicalPath();classLoader.addPermission(contextRoot);} catch (IOException e) {// Ignore}}URL classesURL = servletContext.getResource("/WEB-INF/classes/");classLoader.addPermission(classesURL);URL libURL = servletContext.getResource("/WEB-INF/lib/");classLoader.addPermission(libURL);if (contextRoot != null) {if (libURL != null) {File rootDir = new File(contextRoot);File libDir = new File(rootDir, "WEB-INF/lib/");try {String path = libDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}} else {if (workDir != null) {if (libURL != null) {File libDir = new File(workDir, "WEB-INF/lib/");try {String path = libDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}if (classesURL != null) {File classesDir = new File(workDir, "WEB-INF/classes/");try {String path = classesDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}}}} catch (MalformedURLException e) {}} 【4.5】開啟新線程執行類的重新載入 1)WebappLoader類支持自動重載功能:如果倉庫中的類被重新編譯了,那么這個類會自動重新載入,無需重啟tomcat; 2)為了實現這個功能:WebappLoader類使用一個線程周期性地檢查每個資源的時間戳。間隔時間由變量 checkInterval 指定,單位為妙。默認case下, checkInterval的值為15,即每隔15秒會檢查一次是否有文件需要自動重新載入。 getCheckInterval方法和setCheckInterval方法 用于獲取和設置間隔時間; 3)tomcat4中,WebappLoader類實現 java.lang.Runnable 接口來支持自動重載,源代碼如下: 對以上代碼的分析(Analysis)(run方法中while循環會執行以下operations): o1)使線程休眠一段時間,時長由變量checkInterval指定,以秒為單位; o2)調用WebappLoader 實例的類載入器的modified方法檢查已經載入的類是否被修改,若沒有類修改,則重新執行循環; o3)若某個已經載入的類被修改了,則調用私有方法 notifyContext(),通知與 WebappLoader實例關聯的 Context容器重新載入相關類;
Attention)緊接上面的調用流程圖,last step 是調用Context容器的reload() 方法,其源代碼如下(本文應用程序的Context容器的實現示例是StandardContext): public synchronized void reload() { //org.apache.catalina.core.StandardContext.reload()// Validate our current component stateif (!started)throw new IllegalStateException(sm.getString("containerBase.notStarted", logName()));// Make sure reloading is enabled// if (!reloadable)// throw new IllegalStateException// (sm.getString("standardContext.notReloadable"));log(sm.getString("standardContext.reloadingStarted"));// Stop accepting requests temporarilysetPaused(true);// Binding threadClassLoader oldCCL = bindThread();// Shut down our session managerif ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingManager"), e);}}// Shut down the current version of all active servletsContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingWrapper",wrapper.getName()),e);}}}// Shut down application event listenerslistenerStop();// Clear all application-originated servlet context attributesif (context != null)context.clearAttributes();// Shut down filtersfilterStop();if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.STOP_EVENT));}// Binding threadunbindThread(oldCCL);// Shut down our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingLoader"), e);}}// Binding threadoldCCL = bindThread();// Restart our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingLoader"), e);}}// Binding threadunbindThread(oldCCL);// Create and register the associated naming context, if internal// naming is usedboolean ok = true;if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.START_EVENT));}// Binding threadoldCCL = bindThread();// Restart our application event listeners and filtersif (ok) {if (!listenerStart()) {log(sm.getString("standardContext.listenerStartFailed"));ok = false;}}if (ok) {if (!filterStart()) {log(sm.getString("standardContext.filterStartFailed"));ok = false;}}// Restore the "Welcome Files" and "Resources" context attributespostResources();postWelcomeFiles();// Restart our currently defined servletsfor (int i = 0; i < children.length; i++) {if (!ok)break;Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingWrapper",wrapper.getName()),e);ok = false;}}}// Reinitialize all load on startup servletsloadOnStartup(children);// Restart our session manager (AFTER naming context recreated/bound)if ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingManager"), e);}}// Unbinding threadunbindThread(oldCCL);// Start accepting requests againif (ok) {log(sm.getString("standardContext.reloadingCompleted"));} else {setAvailable(false);log(sm.getString("standardContext.reloadingFailed"));}setPaused(false);// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(Context.RELOAD_EVENT, null);} 【5】WebappClassLoader類 1)web 應用程序中負責載入類的類載入器是: org.apache.catalina.loader.WebappLoader類的實例; 2)考慮到安全性:WebappClassLoader 類不允許載入指定的某些類,這些類的名字存儲在一個字符串數組變量triggers中,當前只有一個元素: private static final String[] triggers = { "javax.servlet.Servlet" }; 3)還有,某些特殊的包及其子包下的類也是不允許載入的,也不會將載入類的任務委托給系統類載入器去執行: private static final String[] packageTriggers = {"javax","org.xml.sax","org.w3c.dom","org.apache.xerces","org.apache.xalan", }; (下面說明WebappClassLoader類是如何完成待加載類的緩存和載入任務的。)
【5.1】類緩存 1)為了達到更好的性能,會緩存已經載入的類:緩存可以在本地執行,即可以由WebappClassLoader 實例來管理它所加載并緩存的類。 1.1)java.lang.ClassLoader類會維護一個Vector對象,保存已經載入的類,防止這些類在不使用時當做垃圾而回收; 2)資源:每個由 WebappClassLoader 載入的類(無論是在WEB-INF/classes 目錄下還是從某個JAR 文件內作為類文件部署), 都視為資源;資源是 org.apache.catalina.loader.ResourceEntry 類的實例;(干貨——資源的定義——每個由 WebappClassLoader 載入的類都是資源) 3)資源類ResourceEntry類的源代碼定義為: public class ResourceEntry { // org.apache.catalina.loader.ResourceEntry/*** The "last modified" time of the origin file at the time this class* was loaded, in milliseconds since the epoch.*/public long lastModified = -1;/*** Binary content of the resource.*/public byte[] binaryContent = null;/*** Loaded class.*/public Class loadedClass = null;/*** URL source from where the object was loaded.*/public URL source = null;/*** URL of the codebase from where the object was loaded.*/public URL codeBase = null;/*** Manifest (if the resource was loaded from a JAR).*/public Manifest manifest = null;/*** Certificates (if the resource was loaded from a JAR).*/public Certificate[] certificates = null; }
4)所有已經緩存的類會存儲在一個名為resourceEntries 的 HashMap 類型的變量中,其key值就是載入的資源名稱。那些載入失敗的類會被存儲到另一個名為 notFoundResources 的 HashMap 類型的變量中;
【5.2】載入類(當載入類時,WebappClassLoader類要遵守如下rules) r1)因為所有已經載入的類都會緩存起來,所以載入類時要先檢查本地緩存; r2)若本地緩存中沒有,則檢查上一層緩存,即調用 java.lang.ClassLoader 類的findLoadedClass() 方法; r3)若兩個緩存中都沒有,則使用系統的類載入器進行加載,防止 web 應用程序中的類覆蓋J2EE 的類; r4)若啟用了 SecurityManager,則檢查是否允許載入該類。若該類是禁止載入的類,拋出 ClassNotFoundException異常; r5)若打開標志位 delegate,或者待載入的類是屬于包觸發器中的包名,則調用父載入器來載入相關類。如果父載入器是null,則使用系統的類載入器; r6)從當前倉庫中載入相關的類; r7)若當前倉庫中沒有需要的類,且標志位delegate關閉,則使用父類載入器。若父類載入器為 null, 則使用系統的類載入器進行加載; r8)若仍未找到需要的類,則拋出 ClassNotFoundException 異常;
【5.3】應用程序 1)應用程序目的是為了說明:如何使用與某個Context容器相關聯的WebappLoader實例;(干貨——本應用程序的目的) 2)而Context接口的標準實現:org.apache.catalina.core.StandardContext;(干貨——你也看到了,從本文起,tomcat的Context容器的標準實現變為了StandardContext,而不是原來diy出的 SimpleContext) 3)我們需要知道的是:StandardContext類是如何與監聽它觸發的事件(如START_EVENT and STOP_EVENT)的監聽器協同工作的;(干貨——需要理清StandardContext類的工作原理) 4)監聽器必須實現接口: org.apache.catalina.lifecycle.LifecycleListener接口;而監聽器的一個實例是 SimpleContextConfig類; 5)應用程序源代碼: public final class Bootstrap {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive // 為了通知StandardContext 實例到哪里查找應用程序目錄,需要設置一個名為"catalina.base"的系統屬性,其值為"user.dir"屬性的值;System.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector(); //實例化默認連接器Wrapper wrapper1 = new SimpleWrapper(); // 為兩個servlet類創建兩個Wrapper實例;wrapper1.setName("Primitive");wrapper1.setServletClass("PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("ModernServlet");// 創建StandardContext 的一個實例,設置應用程序路徑和上下文的文檔根路徑Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");// 上面的代碼在功能上等同于下面的在server.xml 文件中的配置// <Context path="/myApp" docBase="myApp" />context.addChild(wrapper1); // 將兩個Wrapper實例添加到Context容器中context.addChild(wrapper2);// 為它們設置訪問路徑的映射關系,這樣Context 容器就能夠定位到他們// context.addServletMapping(pattern, name); context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't start// 下一步,實例化一個監聽器,并通過 Context容器注冊它.LifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);// 接著,它會實例化WebappLoader類,并將其關聯到Context容器.// here is our loaderLoader loader = new WebappLoader();// associate the loader with the Contextcontext.setLoader(loader);// 然后,將Context容器與默認連接器相關聯,調用默認連接器的initialize() and start()方法,// 再調用 Context容器的 start() 方法,這樣servlet容器準備就緒,可以處理servlet請求了.connector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start(); ((Lifecycle) context).start(); // attention: 從這一行開始(包括這一行),進行spec analysis.// 接下來的幾行代碼僅僅顯示出資源的docBase屬性值和類載入器中所有的倉庫的名字.// now we want to know some details about WebappLoaderWebappClassLoader classLoader = (WebappClassLoader) loader.getClassLoader();System.out.println("Resources' docBase: " + ((ProxyDirContext)classLoader.getResources()).getDocBase());String[] repositories = classLoader.findRepositories();for (int i=0; i<repositories.length; i++) {System.out.println(" repository: " + repositories[i]);}// 最后,用戶輸入任意鍵后,程序退出.// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}} } Conclusion) C1)web 應用程序中的載入器,或一個簡單的載入器,都是 Catalina中最重要的組件;(干貨——載入器是Catalina中最重要的組件) C2)載入器負責載入應用程序所需要的類,因此會使用一個內部類載入器:這個內部類載入器是一個自定義類,tomcat使用這個自定義的類載入器對 web應用程序上下文中要載入的類進行一些約束; C3)此外,自定義類載入器可以支持對載入類的緩存和對一個或多個被修改的類的自動重載;
6)打印結果 E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-resources.jar;lib/naming-common.jar;lib/commons-collectio ns.jar;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomcat.chapter8.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp Starting Wrapper Primitive Starting Wrapper Modern StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom StandardManager[/myApp]: Seeding of random number generator has been completed Resources' docBase: E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\myApp // this line.Stopping wrapper Primitive Stopping wrapper ModernE:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>Attention)這里還差了一個打印info, 在this line 下面一行: repository: /WEB-INF/classes/ ;
 
                            
                        
                        
                        【1】?java的類載入器 0)intro to 類載入器: 每次創建java類的實例時,都必須先將類載入到內存中, java虛擬機使用類載入器來載入需要的類。一般case下, 類載入器會在一些java 核心類庫,以及環境變量 classpath 中指明的目錄中 搜索相關類。如果在這些位置都找不到要載入的類,就會拋出 java.lang.ClassNotFoundException 異常; 1)從J2SE 1.2 開始, jvm 使用了3種類載入器來載入所需要的類:分別是引導類載入器(bootstrap class loader), 擴展類載入器(extension?class loader) 和 系統類載入器(system?class loader)。而 引導類載入器是 擴展類載入器的父親,?擴展類載入器是 系統類載入器的父親。(干貨——jvm 使用了3種類載入器來載入所需要的類)
2)3種載入器的詳細描述:(干貨——3種載入器的 spec intro) 2.1)引導類載入器: 用于引導啟動 jvm。當調用 javax.exe 是, 就會啟動引導類載入器。引導類載入器是使用本地代碼來實現的, 因為它用來載入運行 jvm 所需要的類, 以及所有的 java 核心類。如 java.lang 包 和 java.io 包下的類。啟動類載入器會在 rt.jar 和 i18n.jar 等java 包中搜索要載入的類。 2.2) 擴展類載入器: 負責載入標準擴展目錄中的類。sum 公司的 jvm 的標準擴展目錄是 /jdk/jre/lib/ext/; 2.3)系統類載入器:是默認的類載入器, 他會搜索在環境變量 CLASSPATH 中指明的路徑和 JAR 文件; 3)jvm 使用的是哪種類載入器呢? 3.1)答案在于 類載入器的代理模型。 3.2)載入一個類 的steps(每當需要載入一個類 的時候):(干貨中的干貨——載入一個類 的steps) step1)首先調用 系統類載入器,但并不會立即載入這個類; step2)相反,他會將載入類的任務交給其父類載入器——擴展類載入器; step3)而擴展類載入器也會將載入任務交給其父類載入器——引導類載入器; 3.3)因此,引導類載入器會首先執行載入某個類的任務。接下來有3種cases: case1)如果引導類載入器找不到需要載入的類,那么擴展類載入器會嘗試 載入該類; case2)如果擴展類載入器也找不到該類,就輪到系統類載入器繼續執行載入任務; case3)如果系統類載入器也找不到這個類,拋出 java.lang.ClassNotFoundException 異常;
3.4)為什么要這么做? 代理模型的重要用途就是為了 解決 類載入過程中的安全問題;(干貨——代理模型的重要用途) 3.5)看個荔枝: 當程序的某個地方調用了 自定義的 java.lang.Object 類時, 系統類載入器會將載入工作 委托給 擴展類載入器,繼而會被交給 引導類載入器。 引導類載入器搜索其 核心庫, 找到標準的 java.lang.Object 類, 并將之實例化。 結果是, 自定義的 java.lang.Object 類并沒有被載入(這正是我們想要的)。
4)關于 java 中類載入機制的一件重要事情是, 可以通過繼承抽象類 java.lang.ClassLoader 類 編寫自己的類載入器。而 tomcat 要使用自定義類載入器的原因有3條(reasons):(干貨——tomcat 要使用自定義類載入器的原因) r1)為了在載入類中指定某些規則; r2)為了緩存已經載入的類; r3)為了實現類的預載入,方便使用;
【2】Loader接口 0)載入web 應用程序中需要的servlet類及其相關類需要遵循的一些rules:(干貨——web app中用到的servlet需要遵循的一些rules) r1)應用程序中的 servlet 只能引用部署在 WEB-INF/classes 目錄及其子目錄下的類; r2)但是,servlet類不能訪問其他路徑中的類,即使這些類包含在運行當前的Tomcat的 jvm 的 classpath 環境變量中; r3)此外, servlet類只能訪問 WEB-INF/lib 目錄下的庫,其他目錄中的類庫不能訪問; 1)Tomcat載入器指的是web 應用程序載入器,而不僅僅指 類載入器:(干貨——tomcat載入器不僅僅是類載入器) 1.1)載入器必須實現 org.apache.catalina.Loader接口; 1.2)載入器的實現中,會使用一個 自定義類載入器: 它是 org.apache.catalina.loader.WebappClassLoader類的一個實例;(Loader接口的getClassLoader() 方法來獲取) 2)Loader接口定義的對倉庫集合的操作:(Tomcat中的倉庫就是WEB-INFO/classes 目錄和 WEB-INF/lib 目錄) 2.1)intro to 倉庫:一個web app的倉庫指的是,其 WEB-INFO/classes 目錄和 WEB-INF/lib 目錄,這兩個目錄作為倉庫添加到 載入器中; 2.2)addRepository方法和 findRepositories() 方法:添加一個新倉庫和返回所有倉庫集合(數組對象); 3)Tomcat的載入器通常會與一個 Context級別的servlet容器相關聯; 4)自動重載:如果Context 容器中的一個或多個類被修改了, 載入器也可以支持對類的自動重載; 4.1)Loader接口使用modified() 方法來支持類的自動重載:如果倉庫中的一個或多個類被修改了,那么modified() 方法會返回true,才能提供自動重載的支持; 4.2)載入器類本身并不會自動重載: 它會調用 Context接口(容器)的reload() 方法來實現; 4.3)setReloadable() 和 getReloadable() 方法:用來指明是否支持載入器的自動重載; 4.4)自動重載的default case:默認情況下是 禁用了自動重載的功能的,要想啟動Context容器的自動重載功能,需要再 server.xml 文件中添加一個 Context元素,如下所示:(干貨——默認case下,Context容器的自動重載功能是closed,通過如下方式啟用自動重載功能) <Context path="/myApp" docBase="myApp" debug="0" reloadable="true" /> 4.5)載入器的實現會指明是否要委托給一個父類載入器;(Loader接口中聲明了 getDelegate()方法 和 setDelegate()方法) 4.6)org.apache.catalina.Loader的聲明代碼如下: package org.apache.catalina; import java.beans.PropertyChangeListener;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); } 5)Catalina提供了 org.apache.catalina.loader.WebappLoader類作為 Loader接口的實現:?WebappLoader 對象中使用 org.apache.catalina.loader.WebappClassLoader 類的實例作為其類載入器,該類繼承自 java.net.URLClassLoader類;(干貨——WebappClassLoader類很重要,已經提及過兩次了) 6)Loader接口及其實現類的URL類圖如下:
Attention)當與某個載入器相關聯的容器(如 Context)需要使用某個 servlet類時,即當該類的某個方法被調用時, 容器會先調用載入器的 getClassLoader() 方法來獲取類載入器的實例。然后,容器會調用類 載入器的 loadClass() 方法來載入這個servlet類;
【3】Reloader接口 1)intro to Reloader接口:為了支持類的自動重載功能,類載入器實現需要實現 org.apache.catalina.loader.Reloader接口; 2)最重要的方法:modified方法,其作用是,如果web app 中的某個servlet 或相關類被修改了,modified方法會返回true;(干貨——Reloader接口的最重要的方法modified方法)
【4】 WebappLoader類(web 應用程序載入器, 負責載入web 應用程序中所使用到的類) 1)如上述的UML類圖所示,WebappLoader類實現 Runnable接口,當調用WebappLoader類的start方法時,會完成以下幾項重要工作(works):
w1)創建一個類載入器; w2)設置倉庫; w3)設置類路徑; w4)設置訪問權限; w5)啟動一個新線程來支持自動重載; (下面的內容將分別對以上works 進行 intro) public void start() throws LifecycleException {// org.apache.catalina.loader.WebappLoader.start(),為了看其 outline,我沒有對該method做刪減// Validate and update our current component stateif (started)throw new LifecycleException(sm.getString("webappLoader.alreadyStarted"));if (debug >= 1)log(sm.getString("webappLoader.starting"));lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;if (container.getResources() == null)return;// Register a stream handler factory for the JNDI protocolURLStreamHandlerFactory streamHandlerFactory =new DirContextURLStreamHandlerFactory();try {URL.setURLStreamHandlerFactory(streamHandlerFactory);} catch (Throwable t) {// Ignore the error here.}// Construct a class loader based on our current repositories listtry {classLoader = createClassLoader(); // 創建一個類載入器,下面是設置類載入器classLoader.setResources(container.getResources()); classLoader.setDebug(this.debug);classLoader.setDelegate(this.delegate);for (int i = 0; i < repositories.length; i++) {classLoader.addRepository(repositories[i]);}// Configure our repositoriessetRepositories(); // 設置倉庫setClassPath(); // 設置類路徑setPermissions(); // 設置訪問權限if (classLoader instanceof Lifecycle)((Lifecycle) classLoader).start(); // Binding the Webapp class loader to the directory contextDirContextURLStreamHandler.bind((ClassLoader) classLoader, this.container.getResources());} catch (Throwable t) {throw new LifecycleException("start: ", t);}// Validate that all required packages are actually availablevalidatePackages();// Start our background thread if we are reloadableif (reloadable) {log(sm.getString("webappLoader.reloading"));try {threadStart(); // 啟動一個新線程來支持自動重載} catch (IllegalStateException e) {throw new LifecycleException(e);}}}
【4.1】創建類載入器 1)WebappLoader類提供了 getLoaderClass() 方法 和 setLoaderClass() 方法來獲取或改變 其私有變量的 loaderClass 的值; 1.1)該私有變量保存了一個字符串類型的值,指明了類載入器所要載入的類的名字;(干貨——私有變量loaderClass指明了類載入器所要載入的類的名字) 1.2)默認情況下: 變量loadClass的值是 org.apache.catalina.loader.WebappClassLoader ; 1.3)也可以通過繼承 WebappClassLoader 類的方式實現自己的類載入器,然后調用 setLoaderClass() 方法強制WebappLoader實例使用 自定義類載入器。否則的話,在它啟動時,WebappLoader 類會調用其 私有方法 createClassLoader() 方法來創建 默認 的 ?類載入器; Attention) A1)可以不使用 WebappClassLoader 類的實例,而使用其他類的實例作為類載入器; A2)createClassLoader()方法的返回值是: WebappLoader類型的, 因此,如果自定義類型沒有繼承自 WebappClassLoader l類,createClassLoader方法就會拋出一個異常; private WebappClassLoader createClassLoader()throws Exception { // org.apache.catalina.loader.WebappLoader.createClassLoader()Class clazz = Class.forName(loaderClass);WebappClassLoader classLoader = null; // highlight line.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;} 【4.2】設置倉庫 private void setRepositories() { // org.apache.catalina.loader.WebappLoader.setRepositories()if (!(container instanceof Context))return;ServletContext servletContext =((Context) container).getServletContext();if (servletContext == null)return;// Loading the work directoryFile workDir =(File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);if (workDir == null)return;log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));DirContext resources = container.getResources();// Setting up the class repository (/WEB-INF/classes), if it existsString classesPath = "/WEB-INF/classes";DirContext classes = null;try {Object object = resources.lookup(classesPath);if (object instanceof DirContext) {classes = (DirContext) object;}} catch(NamingException e) {// Silent catch: it's valid that no /WEB-INF/classes collection// exists}if (classes != null) {File classRepository = null;String absoluteClassesPath =servletContext.getRealPath(classesPath);if (absoluteClassesPath != null) {classRepository = new File(absoluteClassesPath);} else {classRepository = new File(workDir, classesPath);classRepository.mkdirs();copyDir(classes, classRepository);}log(sm.getString("webappLoader.classDeploy", classesPath,classRepository.getAbsolutePath()));// Adding the repository to the class loaderclassLoader.addRepository(classesPath + "/", classRepository);}// Setting up the JAR repository (/WEB-INF/lib), if it existsString libPath = "/WEB-INF/lib";classLoader.setJarPath(libPath);DirContext libDir = null;// Looking up directory /WEB-INF/lib in the contexttry {Object object = resources.lookup(libPath);if (object instanceof DirContext)libDir = (DirContext) object;} catch(NamingException e) {// Silent catch: it's valid that no /WEB-INF/lib collection// exists}if (libDir != null) {boolean copyJars = false;String absoluteLibPath = servletContext.getRealPath(libPath);File destDir = null;if (absoluteLibPath != null) {destDir = new File(absoluteLibPath);} else {copyJars = true;destDir = new File(workDir, libPath);destDir.mkdirs();}// Looking up directory /WEB-INF/lib in the contexttry {NamingEnumeration enum = resources.listBindings(libPath);while (enum.hasMoreElements()) {Binding binding = (Binding) enum.nextElement();String filename = libPath + "/" + binding.getName();if (!filename.endsWith(".jar"))continue;// Copy JAR in the work directory, always (the JAR file// would get locked otherwise, which would make it// impossible to update it or remove it at runtime)File destFile = new File(destDir, binding.getName());log(sm.getString("webappLoader.jarDeploy", filename,destFile.getAbsolutePath()));Resource jarResource = (Resource) binding.getObject();if (copyJars) {if (!copy(jarResource.streamContent(),new FileOutputStream(destFile)))continue;}JarFile jarFile = new JarFile(destFile);classLoader.addJar(filename, jarFile, destFile);}} catch (NamingException e) {// Silent catch: it's valid that no /WEB-INF/lib directory// exists} catch (IOException e) {e.printStackTrace();} }} 【4.3】設置類路徑 1)設置類路徑的任務是:通過在 start()方法中調用 setClassPath方法完成的。該方法會在servlet上下文中 為 Jasper JSP 編譯器設置一個字符串形式的屬性來指明類路徑信息; private void setClassPath() { // org.apache.catalina.loader.WebappLoader.setClassPath()// Validate our current state informationif (!(container instanceof Context))return;ServletContext servletContext =((Context) container).getServletContext();if (servletContext == null)return;StringBuffer classpath = new StringBuffer();// Assemble the class path information from our class loader chainClassLoader loader = getClassLoader();int layers = 0;int n = 0;while ((layers < 3) && (loader != null)) {if (!(loader instanceof URLClassLoader))break;URL repositories[] =((URLClassLoader) loader).getURLs();for (int i = 0; i < repositories.length; i++) {String repository = repositories[i].toString();if (repository.startsWith("file://"))repository = repository.substring(7);else if (repository.startsWith("file:"))repository = repository.substring(5);else if (repository.startsWith("jndi:"))repository =servletContext.getRealPath(repository.substring(5));elsecontinue;if (repository == null)continue;if (n > 0)classpath.append(File.pathSeparator);classpath.append(repository);n++;}loader = loader.getParent();layers++;}// Store the assembled class path as a servlet context attributeservletContext.setAttribute(Globals.CLASS_PATH_ATTR,classpath.toString());} 【4.4】設置訪問權限 1)若運行Tomcat時,使用了安全管理器:則 setPermissions() 方法會為 類載入器設置訪問相關目錄的權限; private void setPermissions() {// org.apache.catalina.loader.WebappLoader.setPermission()if (System.getSecurityManager() == null)return;if (!(container instanceof Context))return;// Tell the class loader the root of the contextServletContext servletContext =((Context) container).getServletContext();// Assigning permissions for the work directoryFile workDir =(File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);if (workDir != null) {try {String workDirPath = workDir.getCanonicalPath();classLoader.addPermission(new FilePermission(workDirPath, "read,write"));classLoader.addPermission(new FilePermission(workDirPath + File.separator + "-", "read,write,delete"));} catch (IOException e) {// Ignore}} try {URL rootURL = servletContext.getResource("/");classLoader.addPermission(rootURL);String contextRoot = servletContext.getRealPath("/");if (contextRoot != null) {try {contextRoot = (new File(contextRoot)).getCanonicalPath();classLoader.addPermission(contextRoot);} catch (IOException e) {// Ignore}}URL classesURL = servletContext.getResource("/WEB-INF/classes/");classLoader.addPermission(classesURL);URL libURL = servletContext.getResource("/WEB-INF/lib/");classLoader.addPermission(libURL);if (contextRoot != null) {if (libURL != null) {File rootDir = new File(contextRoot);File libDir = new File(rootDir, "WEB-INF/lib/");try {String path = libDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}} else {if (workDir != null) {if (libURL != null) {File libDir = new File(workDir, "WEB-INF/lib/");try {String path = libDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}if (classesURL != null) {File classesDir = new File(workDir, "WEB-INF/classes/");try {String path = classesDir.getCanonicalPath();classLoader.addPermission(path);} catch (IOException e) {}}}}} catch (MalformedURLException e) {}} 【4.5】開啟新線程執行類的重新載入 1)WebappLoader類支持自動重載功能:如果倉庫中的類被重新編譯了,那么這個類會自動重新載入,無需重啟tomcat; 2)為了實現這個功能:WebappLoader類使用一個線程周期性地檢查每個資源的時間戳。間隔時間由變量 checkInterval 指定,單位為妙。默認case下, checkInterval的值為15,即每隔15秒會檢查一次是否有文件需要自動重新載入。 getCheckInterval方法和setCheckInterval方法 用于獲取和設置間隔時間; 3)tomcat4中,WebappLoader類實現 java.lang.Runnable 接口來支持自動重載,源代碼如下: 對以上代碼的分析(Analysis)(run方法中while循環會執行以下operations): o1)使線程休眠一段時間,時長由變量checkInterval指定,以秒為單位; o2)調用WebappLoader 實例的類載入器的modified方法檢查已經載入的類是否被修改,若沒有類修改,則重新執行循環; o3)若某個已經載入的類被修改了,則調用私有方法 notifyContext(),通知與 WebappLoader實例關聯的 Context容器重新載入相關類;
Attention)緊接上面的調用流程圖,last step 是調用Context容器的reload() 方法,其源代碼如下(本文應用程序的Context容器的實現示例是StandardContext): public synchronized void reload() { //org.apache.catalina.core.StandardContext.reload()// Validate our current component stateif (!started)throw new IllegalStateException(sm.getString("containerBase.notStarted", logName()));// Make sure reloading is enabled// if (!reloadable)// throw new IllegalStateException// (sm.getString("standardContext.notReloadable"));log(sm.getString("standardContext.reloadingStarted"));// Stop accepting requests temporarilysetPaused(true);// Binding threadClassLoader oldCCL = bindThread();// Shut down our session managerif ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingManager"), e);}}// Shut down the current version of all active servletsContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingWrapper",wrapper.getName()),e);}}}// Shut down application event listenerslistenerStop();// Clear all application-originated servlet context attributesif (context != null)context.clearAttributes();// Shut down filtersfilterStop();if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.STOP_EVENT));}// Binding threadunbindThread(oldCCL);// Shut down our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingLoader"), e);}}// Binding threadoldCCL = bindThread();// Restart our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingLoader"), e);}}// Binding threadunbindThread(oldCCL);// Create and register the associated naming context, if internal// naming is usedboolean ok = true;if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.START_EVENT));}// Binding threadoldCCL = bindThread();// Restart our application event listeners and filtersif (ok) {if (!listenerStart()) {log(sm.getString("standardContext.listenerStartFailed"));ok = false;}}if (ok) {if (!filterStart()) {log(sm.getString("standardContext.filterStartFailed"));ok = false;}}// Restore the "Welcome Files" and "Resources" context attributespostResources();postWelcomeFiles();// Restart our currently defined servletsfor (int i = 0; i < children.length; i++) {if (!ok)break;Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingWrapper",wrapper.getName()),e);ok = false;}}}// Reinitialize all load on startup servletsloadOnStartup(children);// Restart our session manager (AFTER naming context recreated/bound)if ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingManager"), e);}}// Unbinding threadunbindThread(oldCCL);// Start accepting requests againif (ok) {log(sm.getString("standardContext.reloadingCompleted"));} else {setAvailable(false);log(sm.getString("standardContext.reloadingFailed"));}setPaused(false);// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(Context.RELOAD_EVENT, null);} 【5】WebappClassLoader類 1)web 應用程序中負責載入類的類載入器是: org.apache.catalina.loader.WebappLoader類的實例; 2)考慮到安全性:WebappClassLoader 類不允許載入指定的某些類,這些類的名字存儲在一個字符串數組變量triggers中,當前只有一個元素: private static final String[] triggers = { "javax.servlet.Servlet" }; 3)還有,某些特殊的包及其子包下的類也是不允許載入的,也不會將載入類的任務委托給系統類載入器去執行: private static final String[] packageTriggers = {"javax","org.xml.sax","org.w3c.dom","org.apache.xerces","org.apache.xalan", }; (下面說明WebappClassLoader類是如何完成待加載類的緩存和載入任務的。)
【5.1】類緩存 1)為了達到更好的性能,會緩存已經載入的類:緩存可以在本地執行,即可以由WebappClassLoader 實例來管理它所加載并緩存的類。 1.1)java.lang.ClassLoader類會維護一個Vector對象,保存已經載入的類,防止這些類在不使用時當做垃圾而回收; 2)資源:每個由 WebappClassLoader 載入的類(無論是在WEB-INF/classes 目錄下還是從某個JAR 文件內作為類文件部署), 都視為資源;資源是 org.apache.catalina.loader.ResourceEntry 類的實例;(干貨——資源的定義——每個由 WebappClassLoader 載入的類都是資源) 3)資源類ResourceEntry類的源代碼定義為: public class ResourceEntry { // org.apache.catalina.loader.ResourceEntry/*** The "last modified" time of the origin file at the time this class* was loaded, in milliseconds since the epoch.*/public long lastModified = -1;/*** Binary content of the resource.*/public byte[] binaryContent = null;/*** Loaded class.*/public Class loadedClass = null;/*** URL source from where the object was loaded.*/public URL source = null;/*** URL of the codebase from where the object was loaded.*/public URL codeBase = null;/*** Manifest (if the resource was loaded from a JAR).*/public Manifest manifest = null;/*** Certificates (if the resource was loaded from a JAR).*/public Certificate[] certificates = null; }
4)所有已經緩存的類會存儲在一個名為resourceEntries 的 HashMap 類型的變量中,其key值就是載入的資源名稱。那些載入失敗的類會被存儲到另一個名為 notFoundResources 的 HashMap 類型的變量中;
【5.2】載入類(當載入類時,WebappClassLoader類要遵守如下rules) r1)因為所有已經載入的類都會緩存起來,所以載入類時要先檢查本地緩存; r2)若本地緩存中沒有,則檢查上一層緩存,即調用 java.lang.ClassLoader 類的findLoadedClass() 方法; r3)若兩個緩存中都沒有,則使用系統的類載入器進行加載,防止 web 應用程序中的類覆蓋J2EE 的類; r4)若啟用了 SecurityManager,則檢查是否允許載入該類。若該類是禁止載入的類,拋出 ClassNotFoundException異常; r5)若打開標志位 delegate,或者待載入的類是屬于包觸發器中的包名,則調用父載入器來載入相關類。如果父載入器是null,則使用系統的類載入器; r6)從當前倉庫中載入相關的類; r7)若當前倉庫中沒有需要的類,且標志位delegate關閉,則使用父類載入器。若父類載入器為 null, 則使用系統的類載入器進行加載; r8)若仍未找到需要的類,則拋出 ClassNotFoundException 異常;
【5.3】應用程序 1)應用程序目的是為了說明:如何使用與某個Context容器相關聯的WebappLoader實例;(干貨——本應用程序的目的) 2)而Context接口的標準實現:org.apache.catalina.core.StandardContext;(干貨——你也看到了,從本文起,tomcat的Context容器的標準實現變為了StandardContext,而不是原來diy出的 SimpleContext) 3)我們需要知道的是:StandardContext類是如何與監聽它觸發的事件(如START_EVENT and STOP_EVENT)的監聽器協同工作的;(干貨——需要理清StandardContext類的工作原理) 4)監聽器必須實現接口: org.apache.catalina.lifecycle.LifecycleListener接口;而監聽器的一個實例是 SimpleContextConfig類; 5)應用程序源代碼: public final class Bootstrap {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive // 為了通知StandardContext 實例到哪里查找應用程序目錄,需要設置一個名為"catalina.base"的系統屬性,其值為"user.dir"屬性的值;System.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector(); //實例化默認連接器Wrapper wrapper1 = new SimpleWrapper(); // 為兩個servlet類創建兩個Wrapper實例;wrapper1.setName("Primitive");wrapper1.setServletClass("PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("ModernServlet");// 創建StandardContext 的一個實例,設置應用程序路徑和上下文的文檔根路徑Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");// 上面的代碼在功能上等同于下面的在server.xml 文件中的配置// <Context path="/myApp" docBase="myApp" />context.addChild(wrapper1); // 將兩個Wrapper實例添加到Context容器中context.addChild(wrapper2);// 為它們設置訪問路徑的映射關系,這樣Context 容器就能夠定位到他們// context.addServletMapping(pattern, name); context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't start// 下一步,實例化一個監聽器,并通過 Context容器注冊它.LifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);// 接著,它會實例化WebappLoader類,并將其關聯到Context容器.// here is our loaderLoader loader = new WebappLoader();// associate the loader with the Contextcontext.setLoader(loader);// 然后,將Context容器與默認連接器相關聯,調用默認連接器的initialize() and start()方法,// 再調用 Context容器的 start() 方法,這樣servlet容器準備就緒,可以處理servlet請求了.connector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start(); ((Lifecycle) context).start(); // attention: 從這一行開始(包括這一行),進行spec analysis.// 接下來的幾行代碼僅僅顯示出資源的docBase屬性值和類載入器中所有的倉庫的名字.// now we want to know some details about WebappLoaderWebappClassLoader classLoader = (WebappClassLoader) loader.getClassLoader();System.out.println("Resources' docBase: " + ((ProxyDirContext)classLoader.getResources()).getDocBase());String[] repositories = classLoader.findRepositories();for (int i=0; i<repositories.length; i++) {System.out.println(" repository: " + repositories[i]);}// 最后,用戶輸入任意鍵后,程序退出.// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}} } Conclusion) C1)web 應用程序中的載入器,或一個簡單的載入器,都是 Catalina中最重要的組件;(干貨——載入器是Catalina中最重要的組件) C2)載入器負責載入應用程序所需要的類,因此會使用一個內部類載入器:這個內部類載入器是一個自定義類,tomcat使用這個自定義的類載入器對 web應用程序上下文中要載入的類進行一些約束; C3)此外,自定義類載入器可以支持對載入類的緩存和對一個或多個被修改的類的自動重載;
6)打印結果 E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-resources.jar;lib/naming-common.jar;lib/commons-collectio ns.jar;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomcat.chapter8.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp Starting Wrapper Primitive Starting Wrapper Modern StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom StandardManager[/myApp]: Seeding of random number generator has been completed Resources' docBase: E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\myApp // this line.Stopping wrapper Primitive Stopping wrapper ModernE:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>Attention)這里還差了一個打印info, 在this line 下面一行: repository: /WEB-INF/classes/ ;
總結
以上是生活随笔為你收集整理的tomcat(8)载入器的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: follow 开源项目关于NoClass
- 下一篇: 漫步者耳机蓝牙连接电脑(漫步者耳机蓝牙连
