源碼剖析Tomcat類的加載原理
眾所周知,Java中默認(rèn)的類加載器是以父子關(guān)系存在的,實(shí)現(xiàn)了雙親委派機(jī)制進(jìn)行類的加載,在前文中,我們提到了,雙親委派機(jī)制的設(shè)計(jì)是為了保證類的唯一性,這意味著在同一個(gè)JVM中是不能加載相同類庫(kù)的不同版本的類。
然而與許多服務(wù)器應(yīng)用程序一樣,Tomcat 允許容器的不同部分以及在容器上運(yùn)行的不同Web應(yīng)用程序可以訪問(wèn)的各種不同版本的類庫(kù),這就要求Tomcat必須打破這種雙親委派機(jī)制,通過(guò)實(shí)現(xiàn)自定義的類加載器(即實(shí)現(xiàn)了java.lang.ClassLoader)進(jìn)行類的加載。下面,就讓我們來(lái)看看Tomcat類加載原理是怎樣的。
Tomcat中有兩個(gè)最重要的類加載器,第一個(gè)便是負(fù)責(zé)Web應(yīng)用程序類加載的WebappClassLoader,另一個(gè)便是JSP Servlet類加載器`JasperLoader。
Web應(yīng)用程序類加載器(WebappClassLoader)
上代碼:
public class WebappClassLoader extends WebappClassLoaderBase { public WebappClassLoader() { super(); } public WebappClassLoader(ClassLoader parent) { super(parent); } ... }
我們來(lái)看看WebappClassLoader繼承的WebappClassLoaderBase中實(shí)現(xiàn)的類加載方法loadClass
public abstract class WebappClassLoaderBase extends URLClassLoader implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck { //... 省略不需要關(guān)注的代碼 protected WebappClassLoaderBase() { super(new URL[0]); // 獲取當(dāng)前WebappClassLoader的父加載器系統(tǒng)類加載器 ClassLoader p = getParent(); if (p == null) { p = getSystemClassLoader(); } this.parent = p; // javaseClassLoader變量經(jīng)過(guò)以下代碼的執(zhí)行, // 得到的是擴(kuò)展類加載器(ExtClassLoader) ClassLoader j = String.class.getClassLoader(); if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) { j = j.getParent(); } } this.javaseClassLoader = j; securityManager = System.getSecurityManager(); if (securityManager != null) { refreshPolicy(); } } //...省略不需要關(guān)注的代碼 @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) { log.debug("loadClass(" + name + ", " + resolve + ")"); } Class<?> clazz = null; // Web應(yīng)用程序停止?fàn)顟B(tài)時(shí),不允許加載新的類 checkStateForClassLoading(name); // 如果之前加載過(guò)該類,就可以從Web應(yīng)用程序類加載器本地類緩存中查找, // 如果找到說(shuō)明WebappClassLoader之前已經(jīng)加載過(guò)這個(gè)類 clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // Web應(yīng)用程序本地類緩存中沒(méi)有,可以從系統(tǒng)類加載器緩存中查找, // 如果找到說(shuō)明AppClassLoader之前已經(jīng)加載過(guò)這個(gè)類 clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Returning class from cache"); } if (resolve) { resolveClass(clazz); } return clazz; } // 將類似java.lang.String這樣的類名這樣轉(zhuǎn)換成java/lang/String // 這樣的資源文件名 String resourceName = binaryNameToPath(name, false); // 獲取引導(dǎo)類加載器(BootstrapClassLoader) ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // 引導(dǎo)類加載器根據(jù)轉(zhuǎn)換后的類名獲取資源url,如果url不為空,就說(shuō)明找到要加載的類 URL url; if (securityManager != null) { PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else { url = javaseLoader.getResource(resourceName); } tryLoadingFromJavaseLoader = (url != null); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); tryLoadingFromJavaseLoader = true; } // 首先,從擴(kuò)展類加載器(ExtClassLoader)加載,防止Java核心API庫(kù)被Web應(yīng)用程序類隨意篡改 if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // 當(dāng)使用安全管理器時(shí),允許訪問(wèn)這個(gè)類 if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = sm.getString("webappClassLoader.restrictedPackage", name); log.info(error, se); throw new ClassNotFoundException(error, se); } } } /* * 如果Web應(yīng)用程序類加載器配置為,<Loader delegate="true"/> 或者滿足下列條件的類: * 當(dāng)前類屬于以下這些jar包中: * annotations-api.jar — Common Annotations 1.2 類。 * catalina.jar — Tomcat 的 Catalina servlet 容器部分的實(shí)現(xiàn)。 * catalina-ant.jar — 可選。用于使用 Manager Web 應(yīng)用程序的 Tomcat Catalina Ant 任務(wù)。 * catalina-ha.jar — 可選。提供基于 Tribes 構(gòu)建的會(huì)話集群功能的高可用性包。 * catalina-storeconfig.jar — 可選。從當(dāng)前狀態(tài)生成 XML 配置文件。 * catalina-tribes.jar — 可選。高可用性包使用的組通信包。 * ecj-*.jar — 可選。Eclipse JDT Java 編譯器用于將 JSP 編譯為 Servlet。 * el-api.jar — 可選。EL 3.0 API。 * jasper.jar — 可選。Tomcat Jasper JSP 編譯器和運(yùn)行時(shí)。 * jasper-el.jar — 可選。Tomcat EL 實(shí)現(xiàn)。 * jaspic-api.jar — JASPIC 1.1 API。 * jsp-api.jar — 可選。JSP 2.3 API。 * servlet-api.jar — Java Servlet 3.1 API。 * tomcat-api.jar — Tomcat 定義的幾個(gè)接口。 * tomcat-coyote.jar — Tomcat 連接器和實(shí)用程序類。 * tomcat-dbcp.jar — 可選。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的 * 包重命名副本的數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn)。 * tomcat-i18n-**.jar — 包含其他語(yǔ)言資源包的可選 JAR。由于默認(rèn)包也包含在每個(gè)單獨(dú)的JAR * 中,如果不需要消息國(guó)際化,可以安全地刪除它們。 * tomcat-jdbc.jar — 可選。另一種數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn),稱為 Tomcat JDBC 池。有關(guān)詳細(xì)信息,請(qǐng)參閱 文檔。 * tomcat-jni.jar — 提供與 Tomcat Native 庫(kù)的集成。 * tomcat-util.jar — Apache Tomcat 的各種組件使用的通用類。 * tomcat-util-scan.jar — 提供 Tomcat 使用的類掃描功能。 * tomcat-websocket.jar — 可選。Java WebSocket 1.1 實(shí)現(xiàn) * websocket-api.jar — 可選。Java WebSocket 1.1 API * * 此處的filter方法,實(shí)際上tomcat官方將filter類加載過(guò)濾條件,看作是一種類加載器, * 將其取名為CommonClassLoader */ boolean delegateLoad = delegate || filter(name, true); // 如果ExtClassLoader沒(méi)有獲取到,說(shuō)明是非JRE核心類,那么就從系統(tǒng)類加載器(也稱AppClassLoader // 應(yīng)用程序類加載器)加載 if (delegateLoad) { if (log.isDebugEnabled()) { log.debug(" Delegating to parent classloader1 " + parent); } try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Loading class from parent"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // 從Web應(yīng)用程序的類加載器(也就是WebappClassLoader)中加載類。Web應(yīng)用程序的類加載器是 // 一個(gè)特殊的類加載器,它負(fù)責(zé)從Web應(yīng)用程序的本地庫(kù)中加載類 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) { // Ignore } // 經(jīng)過(guò)上面幾個(gè)步驟還未加載到類,則采用系統(tǒng)類加載器(也稱應(yīng)用程序類加載器)進(jìn)行加載 if (!delegateLoad) { if (log.isDebugEnabled()) { log.debug(" Delegating to parent classloader at end: " + parent); } try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) { log.debug(" Loading class from parent"); } if (resolve) { resolveClass(clazz); } return clazz; } } catch (ClassNotFoundException e) { // Ignore } } } // 最終,還未加載到類,報(bào)類未找到的異常 throw new ClassNotFoundException(name); } //...省略不需要關(guān)注的代碼 }
綜上所述,我們得出WebappClassLoader類加載器打破了雙親委派機(jī)制,自定義類加載類的順序:
- 擴(kuò)展類加載器(ExtClassLoader)加載
- Web應(yīng)用程序類加載器(WebappClassLoader)
- 系統(tǒng)類加載器類(AppClassLoader)
- 公共類加載器類(CommonClassLoader)
如果Web應(yīng)用程序類加載器配置為,,也就是WebappClassLoaderBase類的變量delegate=true時(shí),則類加載順序變?yōu)椋?/p>
- 擴(kuò)展類加載器(ExtClassLoader)加載
- 系統(tǒng)類加載器類(AppClassLoader)
- 公共類加載器類(CommonClassLoader)
- Web應(yīng)用程序類加載器(WebappClassLoader)
JSP類加載器(JasperLoader)
上代碼:
public class JasperLoader extends URLClassLoader { private final PermissionCollection permissionCollection; private final SecurityManager securityManager; // JSP類加載器的父加載器是Web應(yīng)用程序類加載器(WebappClassLoader) public JasperLoader(URL[] urls, ClassLoader parent, PermissionCollection permissionCollection) { super(urls, parent); this.permissionCollection = permissionCollection; this.securityManager = System.getSecurityManager(); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } @Override public synchronized Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz = null; // 從JVM的類緩存中查找 clazz = findLoadedClass(name); if (clazz != null) { if (resolve) { resolveClass(clazz); } return clazz; } // 當(dāng)使用SecurityManager安全管理器時(shí),允許訪問(wèn)訪類 if (securityManager != null) { int dot = name.lastIndexOf('.'); if (dot >= 0) { try { // Do not call the security manager since by default, we grant that package. if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){ securityManager.checkPackageAccess(name.substring(0,dot)); } } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; se.printStackTrace(); throw new ClassNotFoundException(error); } } } // 如果類名不是以org.apache.jsp包名開(kāi)頭的,則采用WebappClassLoader加載 if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) { // Class is not in org.apache.jsp, therefore, have our // parent load it clazz = getParent().loadClass(name); if( resolve ) { resolveClass(clazz); } return clazz; } // 如果是org.apache.jsp包名開(kāi)頭JSP類,就調(diào)用父類URLClassLoader的findClass方法 // 動(dòng)態(tài)加載類文件,解析成Class類,返回給調(diào)用方 return findClass(name); } }
下面是URLClassLoader的findClass方法,具體實(shí)現(xiàn):
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res != null) { try { // 解析類的字節(jié)碼文件生成Class類對(duì)象 return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
從源碼中我們可以看到,JSP類加載原理是,先從JVM類緩存中(也就是Bootstrap類加載器加載的類)加載,如果不是核心類庫(kù)的類,就從Web應(yīng)用程序類加載器WebappClassLoader中加載,如果還未找到,就說(shuō)明是jsp類,則通過(guò)動(dòng)態(tài)解析jsp類文件獲得要加載的類。
經(jīng)過(guò)上面兩個(gè)Tomcat核心類加載器的剖析,我們也就知道了Tomcat類的加載原理了。
下面我們來(lái)總結(jié)一下:Tomcat會(huì)為每個(gè)Web應(yīng)用程序創(chuàng)建一個(gè)WebappClassLoader類加載器進(jìn)行類的加載,不同的類加載器實(shí)例加載的類是會(huì)被認(rèn)為是不同的類,即使它們的類名相同,這樣的話就可以實(shí)現(xiàn)在同一個(gè)JVM下,允許Tomcat容器的不同部分以及在容器上運(yùn)行的不同Web應(yīng)用程序可以訪問(wèn)的各種不同版本的類庫(kù)。
針對(duì)JSP類,會(huì)由專門的JSP類加載器(JasperLoader)進(jìn)行加載,該加載器會(huì)針對(duì)JSP類在每次加載時(shí)都會(huì)解析類文件,Tomcat容器會(huì)啟動(dòng)一個(gè)后臺(tái)線程,定時(shí)檢測(cè)JSP類文件的變化,及時(shí)更新類文件,這樣就實(shí)現(xiàn)JSP文件的熱加載功能。
以上就是源碼剖析Tomcat類的加載原理的詳細(xì)內(nèi)容,更多關(guān)于Tomcat類加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決應(yīng)用啟動(dòng)失敗但tomcat不報(bào)錯(cuò)的方法
這篇文章主要給大家介紹了關(guān)于解決應(yīng)用啟動(dòng)失敗但tomcat不報(bào)錯(cuò)的方法,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-06-06使用Tomcat服務(wù)器運(yùn)行sts時(shí)出現(xiàn)報(bào)錯(cuò)的解決辦法
前幾天在運(yùn)行 Spring ToolSuite 時(shí)出現(xiàn)Starting Tomcat v8.5 Server at localhost' hasencountered a problem.的錯(cuò)誤,所以本文給大家介紹了解決這個(gè)錯(cuò)的方法,需要的朋友可以參考下2023-09-09Centos環(huán)境下Tomcat虛擬主機(jī)配置詳細(xì)教程
這篇文章主要講的是在 CentOS 系統(tǒng)上,如何一步步配置 Tomcat 的虛擬主機(jī),內(nèi)容很簡(jiǎn)單,從目錄準(zhǔn)備到配置文件修改,再到重啟和測(cè)試,手把手帶你搞定,需要的朋友可以參考下2025-03-03Linux中使用Docker容器構(gòu)建Tomcat容器的完整教程
Apache?Tomcat?是一款廣泛使用的開(kāi)源?Java?應(yīng)用服務(wù)器,適用于運(yùn)行?Java?Servlet?和?JSP?應(yīng)用程序,本教程將詳細(xì)介紹如何在?Docker?中構(gòu)建并運(yùn)行一個(gè)?Tomcat?容器,并提供相應(yīng)的步驟和解釋,需要的朋友可以參考下2024-09-09tomcat使用問(wèn)題之安裝后無(wú)法訪問(wèn)localhost:8080解決
當(dāng)Tomcat無(wú)法訪問(wèn)localhost:8080時(shí),可能是由于未啟動(dòng)、環(huán)境變量未配置、端口號(hào)占用或版本問(wèn)題,這篇文章主要介紹了tomcat使用問(wèn)題之安裝后無(wú)法訪問(wèn)localhost:8080解決的相關(guān)資料,需要的朋友可以參考下2024-10-10tomcat多實(shí)例部署的項(xiàng)目實(shí)踐
Tomcat多實(shí)例是指在一臺(tái)設(shè)備上運(yùn)行多個(gè)Tomcat服務(wù),這些Tomcat相互獨(dú)立,本文主要介紹了tomcat多實(shí)例部署的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03Tomcat添加manager用戶的實(shí)現(xiàn)
Tomcat沒(méi)有配置任何默認(rèn)的用戶,因此需要我們進(jìn)行相應(yīng)的用戶配置之后才能使用Tomcat Manager,本文就來(lái)介紹一下Tomcat添加manager用戶的實(shí)現(xiàn),感興趣的可以了解一下2023-10-10linux系統(tǒng)中修改tomcat默認(rèn)輸入日志路徑的方法
本文給大家介紹了linux系統(tǒng)中修改tomcat默認(rèn)輸入日志路徑的方法,文中只是給大家介紹linux系統(tǒng)修改默認(rèn)logs的方法,windows系統(tǒng)類似,需要的朋友可以參考下2018-03-03