繼前文深入剖析雙親委派機制之后,本文將引直接走進具體的代碼實現,一探其真正的實現思路。
Tomcat 啟動的起點在于 Bootstrap 類的 main()方法。在 main()方法執行之前,其靜態代碼塊(static{})會率先被執行。因此,我們將首先深入探討靜態代碼塊的運行機制,然后再分析 main()方法的執行流程。
static { // 獲取用戶目錄 String userDir = System.getProperty("user.dir"); // 優先從環境變量獲取 CATALINA_HOME String home = System.getProperty(Globals.CATALINA_HOME_PROP); File homeFile = null; if (home != null) { File f = new File(home); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } // 若環境變量中未獲取到,則嘗試從 bootstrap.jar 所在目錄的上一級目錄獲取 if (homeFile == null) { File bootstrapJar = new File(userDir, "bootstrap.jar"); if (bootstrapJar.exists()) { File f = new File(userDir, ".."); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } } // 若以上兩種方式均未獲取到,則使用用戶目錄作為 CATALINA_HOME if (homeFile == null) { File f = new File(userDir); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } // 設置 CATALINA_HOME 屬性 catalinaHomeFile = homeFile; System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath()); // 獲取 CATALINA_BASE,若未設置,則使用 CATALINA_HOME 作為 CATALINA_BASE String base = System.getProperty(Globals.CATALINA_BASE_PROP); if (base == null) { catalinaBaseFile = catalinaHomeFile; } else { File baseFile = new File(base); try { baseFile = baseFile.getCanonicalFile(); } catch (IOException ioe) { baseFile = baseFile.getAbsoluteFile(); } catalinaBaseFile = baseFile; } // 設置 CATALINA_BASE 屬性 System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());}
在啟動的初始階段,Tomcat 需要確定自身的根目錄(catalina.home)和工作目錄(catalina.base)。 這段代碼描述了尋找這兩個關鍵路徑的邏輯:
最后,代碼會將最終確定的 catalina.home 和 catalina.base 路徑信息設置為系統屬性,以便在后續的啟動流程中使用。
這段代碼如同為 Tomcat 構建一座穩固的基石,它負責加載并設置 catalina.home 和 catalina.base 相關的信息,為后續的啟動流程奠定基礎。
Tomcat 的 main 方法可概括為兩個主要階段:**初始化 (**init) 和 加載與啟動 (load+start);。
public static void main(String args[]) { // 初始化階段 main方法第一次執行的時候,daemon肯定為null,所以直接new了一個Bootstrap對象,然后執行其init()方法 if (daemon == null) { Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } //daemon守護對象設置為bootstrap daemon = bootstrap; } else { // 當作為服務運行時,對stop的調用將在新線程上進行, // 因此確保使用正確的類加載器以防止一系列類未找到異常。 Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } // 加載與啟動階段 // 執行守護對象的load方法和start方法 try { String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); if (null == daemon.getServer()) { System.exit(1); } } else if (command.equals("stop")) { daemon.stopServer(args); } else if (command.equals("configtest")) { daemon.load(args); if (null == daemon.getServer()) { System.exit(1); } System.exit(0); } else { log.warn("Bootstrap: command /"" + command + "/" does not exist."); } } catch (Throwable t) { // 展開異常以獲得更清晰的錯誤報告 if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); }}
我們點到init()里面去看看~
public void init() throws Exception { // 非常關鍵的地方,初始化類加載器s,后面我們會詳細具體地分析這個方法 initClassLoaders(); // 設置上下文類加載器為catalinaLoader,這個類加載器負責加載Tomcat專用的類 Thread.currentThread().setContextClassLoader(catalinaLoader); // 暫時略過,后面會講 SecurityClassLoad.securityClassLoad(catalinaLoader); // 使用catalinaLoader加載我們的Catalina類 // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); // 設置Catalina類的parentClassLoader屬性為sharedLoader // Set the shared extensions class loader if (log.isDebugEnabled()) log.debug("Setting startup class properties"); 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); // catalina守護對象為剛才使用catalinaLoader加載類、并初始化出來的Catalina對象 catalinaDaemon = startupInstance;}
initClassLoaders 方法是 Tomcat 類加載機制的核心,它負責初始化 Tomcat 的各個類加載器,構建起 Tomcat 獨特的類加載體系。通過分析這個方法,我們可以清晰地驗證上一節提到的 Tomcat 類加載圖。
private void initClassLoaders() { try { // 創建commonLoader,如果未創建成果的話,則使用應用程序類加載器作為commonLoader commonLoader = createClassLoader("common", null); if( commonLoader == null ) { // no config file, default to this loader - we might be in a 'single' env. commonLoader=this.getClass().getClassLoader(); } // 創建catalinaLoader,父類加載器為commonLoader catalinaLoader = createClassLoader("server", commonLoader); // 創建sharedLoader,父類加載器為commonLoader sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { // 如果創建的過程中出現異常了,日志記錄完成之后直接系統退出 handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); }}
createClassLoader 方法是 Tomcat 類加載器創建的底層方法,它負責根據配置文件 catalina.properties 中的配置信息來創建具體的類加載器實例。 CatalinaProperties.getProperty("xxx") 方法用于從 conf/catalina.properties 文件中獲取指定屬性的值,為 createClassLoader 方法提供必要的配置信息。
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { // 獲取類加載器待加載的位置,如果為空,則不需要加載特定的位置,使用父類加載返回回去。 String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; // 替換屬性變量,比如:${catalina.base}、${catalina.home} value = replace(value); List<Repository> repositories = new ArrayList<>(); // 解析屬性路徑變量為倉庫路徑數組 String[] repositoryPaths = getPaths(value); // 對每個倉庫路徑進行repositories設置。我們可以把repositories看成一個個待加載的位置對象,可以是一個classes目錄,一個jar文件目錄等等 for (String repository : repositoryPaths) { // Check for a JAR URL repository try { @SuppressWarnings("unused") URL url = new URL(repository); repositories.add( new Repository(repository, RepositoryType.URL)); continue; } catch (MalformedURLException e) { // Ignore } // Local repository if (repository.endsWith("*.jar")) { repository = repository.substring (0, repository.length() - "*.jar".length()); repositories.add( new Repository(repository, RepositoryType.GLOB)); } else if (repository.endsWith(".jar")) { repositories.add( new Repository(repository, RepositoryType.JAR)); } else { repositories.add( new Repository(repository, RepositoryType.DIR)); } } // 使用類加載器工廠創建一個類加載器 return ClassLoaderFactory.createClassLoader(repositories, parent);}
我們來分析一下ClassLoaderFactory.createClassLoader--類加載器工廠創建類加載器。
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) log.debug("Creating new class loader"); // Construct the "class path" for this class loader Set<URL> set = new LinkedHashSet<>(); // 遍歷repositories,對每個repository進行類型判斷,并生成URL,每個URL我們都要校驗其有效性,有效的URL我們會放到URL集合中 if (repositories != null) { for (Repository repository : repositories) { if (repository.getType() == RepositoryType.URL) { URL url = buildClassLoaderUrl(repository.getLocation()); if (log.isDebugEnabled()) log.debug(" Including URL " + url); set.add(url); } else if (repository.getType() == RepositoryType.DIR) { File directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.DIR)) { continue; } URL url = buildClassLoaderUrl(directory); if (log.isDebugEnabled()) log.debug(" Including directory " + url); set.add(url); } else if (repository.getType() == RepositoryType.JAR) { File file=new File(repository.getLocation()); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } URL url = buildClassLoaderUrl(file); if (log.isDebugEnabled()) log.debug(" Including jar file " + url); set.add(url); } else if (repository.getType() == RepositoryType.GLOB) { File directory=new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.GLOB)) { continue; } if (log.isDebugEnabled()) log.debug(" Including directory glob " + directory.getAbsolutePath()); String filenames[] = directory.list(); if (filenames == null) { continue; } for (int j = 0; j < filenames.length; j++) { String filename = filenames[j].toLowerCase(Locale.ENGLISH); if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } if (log.isDebugEnabled()) log.debug(" Including glob jar file " + file.getAbsolutePath()); URL url = buildClassLoaderUrl(file); set.add(url); } } } } // Construct the class loader itself final URL[] array = set.toArray(new URL[set.size()]); if (log.isDebugEnabled()) for (int i = 0; i < array.length; i++) { log.debug(" location " + i + " is " + array[i]); } // 從這兒看,最終所有的類加載器都是URLClassLoader的對象~~ return AccessController.doPrivileged( new PrivilegedAction<URLClassLoader>() { @Override public URLClassLoader run() { if (parent == null) return new URLClassLoader(array); else return new URLClassLoader(array, parent); } });}
對 initClassLoaders 分析完,Tomcat 還會進行一項至關重要的安全措施,即 SecurityClassLoad.securityClassLoad 方法。 讓我們深入探究這個方法,看看它在 Tomcat 的安全體系中扮演著怎樣的角色。
public static void securityClassLoad(ClassLoader loader) throws Exception { securityClassLoad(loader, true);}static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception { if (requireSecurityManager && System.getSecurityManager() == null) { return; } loadCorePackage(loader); loadCoyotePackage(loader); loadLoaderPackage(loader); loadRealmPackage(loader); loadServletsPackage(loader); loadSessionPackage(loader); loadUtilPackage(loader); loadValvesPackage(loader); loadJavaxPackage(loader); loadConnectorPackage(loader); loadTomcatPackage(loader);} private static final void loadCorePackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.core."; loader.loadClass(basePackage + "AccessLogAdapter"); loader.loadClass(basePackage + "ApplicationContextFacade$PrivilegedExecuteMethod"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedForward"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedInclude"); loader.loadClass(basePackage + "ApplicationPushBuilder"); loader.loadClass(basePackage + "AsyncContextImpl"); loader.loadClass(basePackage + "AsyncContextImpl$AsyncRunnable"); loader.loadClass(basePackage + "AsyncContextImpl$DebugException"); loader.loadClass(basePackage + "AsyncListenerWrapper"); loader.loadClass(basePackage + "ContainerBase$PrivilegedAddChild"); loadAnonymousInnerClasses(loader, basePackage + "DefaultInstanceManager"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntry"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntryType"); loader.loadClass(basePackage + "ApplicationHttpRequest$AttributeNamesEnumerator");}
這段代碼使用 catalinaLoader 加載了 Tomcat 源代碼中各個專用類,這些類主要分布在以下幾個包中:
至此,我們已經逐一分析了 init 方法中的關鍵方法,包括 initClassLoaders、SecurityClassLoad.securityClassLoad 等,了解了 Tomcat 初始化階段的各個步驟。
在深入探究 Tomcat 啟動流程的過程中,我們發現似乎遺漏了一個關鍵角色——WebApp 類加載器。
WebApp 類加載器是每個 Web 應用獨有的,而每個 Web 應用本質上就是一個 Context。 因此,我們理所當然地將目光投向了 Context 的實現類。 Tomcat 中,StandardContext 是 Context 的默認實現,而 WebApp 類加載器正是誕生于 StandardContext 類的 startInternal() 方法中。
protected synchronized void startInternal() throws LifecycleException { if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); }}
這段代碼的邏輯非常簡潔,它在 WebApp 類加載器不存在的情況下,會創建一個新的 WebApp 類加載器,并將其設置為當前 Context 的加載器(setLoader)。
到這里,我們已經完成了對 Tomcat 啟動過程和類加載機制的全面解析,從 Bootstrap 類開始,一步步深入,最終揭開了 WebApp 類加載器的面紗。
通過這個分析過程,我們不僅了解了 Tomcat 啟動和類加載的具體步驟,更深刻地理解了 Tomcat 采用這種獨特的多層級類加載機制的深層原因,以及這種設計帶來的種種優勢。
本文鏈接:http://www.tebozhan.com/showinfo-26-112754-0.html類加載機制的源碼解讀
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com