源碼剖析Tomcat類的加載原理
眾所周知,Java中默認的類加載器是以父子關系存在的,實現了雙親委派機制進行類的加載,在前文中,我們提到了,雙親委派機制的設計是為了保證類的唯一性,這意味著在同一個JVM中是不能加載相同類庫的不同版本的類。
然而與許多服務器應用程序一樣,Tomcat 允許容器的不同部分以及在容器上運行的不同Web應用程序可以訪問的各種不同版本的類庫,這就要求Tomcat必須打破這種雙親委派機制,通過實現自定義的類加載器(即實現了java.lang.ClassLoader)進行類的加載。下面,就讓我們來看看Tomcat類加載原理是怎樣的。
Tomcat中有兩個最重要的類加載器,第一個便是負責Web應用程序類加載的WebappClassLoader,另一個便是JSP Servlet類加載器`JasperLoader。
Web應用程序類加載器(WebappClassLoader)上代碼:
public class WebappClassLoader extends WebappClassLoaderBase { public WebappClassLoader() {super(); } public WebappClassLoader(ClassLoader parent) {super(parent); } ...}我們來看看WebappClassLoader繼承的WebappClassLoaderBase中實現的類加載方法loadClass
public abstract class WebappClassLoaderBase extends URLClassLoaderimplements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {//...省略不需要關注的代碼 protected WebappClassLoaderBase() {super(new URL[0]);// 獲取當前WebappClassLoader的父加載器系統類加載器ClassLoader p = getParent();if (p == null) { p = getSystemClassLoader();}this.parent = p;// javaseClassLoader變量經過以下代碼的執行,// 得到的是擴展類加載器(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();} } //...省略不需要關注的代碼 @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應用程序停止狀態時,不允許加載新的類 checkStateForClassLoading(name); // 如果之前加載過該類,就可以從Web應用程序類加載器本地類緩存中查找,// 如果找到說明WebappClassLoader之前已經加載過這個類 clazz = findLoadedClass0(name); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Returning class from cache');}if (resolve) { resolveClass(clazz);}return clazz; } // Web應用程序本地類緩存中沒有,可以從系統類加載器緩存中查找,// 如果找到說明AppClassLoader之前已經加載過這個類 clazz = findLoadedClass(name); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Returning class from cache');}if (resolve) { resolveClass(clazz);}return clazz; }// 將類似java.lang.String這樣的類名這樣轉換成java/lang/String// 這樣的資源文件名 String resourceName = binaryNameToPath(name, false);// 獲取引導類加載器(BootstrapClassLoader) ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // 引導類加載器根據轉換后的類名獲取資源url,如果url不為空,就說明找到要加載的類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; } // 首先,從擴展類加載器(ExtClassLoader)加載,防止Java核心API庫被Web應用程序類隨意篡改 if (tryLoadingFromJavaseLoader) {try { clazz = javaseLoader.loadClass(name); if (clazz != null) {if (resolve) { resolveClass(clazz);}return clazz; }} catch (ClassNotFoundException e) { // Ignore} } // 當使用安全管理器時,允許訪問這個類 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應用程序類加載器配置為,<Loader delegate='true'/> 或者滿足下列條件的類: * 當前類屬于以下這些jar包中: * annotations-api.jar — Common Annotations 1.2 類。 * catalina.jar — Tomcat 的 Catalina servlet 容器部分的實現。 * catalina-ant.jar — 可選。用于使用 Manager Web 應用程序的 Tomcat Catalina Ant 任務。 * catalina-ha.jar — 可選。提供基于 Tribes 構建的會話集群功能的高可用性包。 * catalina-storeconfig.jar — 可選。從當前狀態生成 XML 配置文件。 * catalina-tribes.jar — 可選。高可用性包使用的組通信包。 * ecj-*.jar — 可選。Eclipse JDT Java 編譯器用于將 JSP 編譯為 Servlet。 * el-api.jar — 可選。EL 3.0 API。 * jasper.jar — 可選。Tomcat Jasper JSP 編譯器和運行時。 * jasper-el.jar — 可選。Tomcat EL 實現。 * 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 定義的幾個接口。 * tomcat-coyote.jar — Tomcat 連接器和實用程序類。 * tomcat-dbcp.jar — 可選。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的 * 包重命名副本的數據庫連接池實現。 * tomcat-i18n-**.jar — 包含其他語言資源包的可選 JAR。由于默認包也包含在每個單獨的JAR * 中,如果不需要消息國際化,可以安全地刪除它們。 * tomcat-jdbc.jar — 可選。另一種數據庫連接池實現,稱為 Tomcat JDBC 池。有關詳細信息,請參閱 文檔。 * tomcat-jni.jar — 提供與 Tomcat Native 庫的集成。 * tomcat-util.jar — Apache Tomcat 的各種組件使用的通用類。 * tomcat-util-scan.jar — 提供 Tomcat 使用的類掃描功能。 * tomcat-websocket.jar — 可選。Java WebSocket 1.1 實現 * websocket-api.jar — 可選。Java WebSocket 1.1 API * * 此處的filter方法,實際上tomcat官方將filter類加載過濾條件,看作是一種類加載器, *將其取名為CommonClassLoader */ boolean delegateLoad = delegate || filter(name, true); // 如果ExtClassLoader沒有獲取到,說明是非JRE核心類,那么就從系統類加載器(也稱AppClassLoader// 應用程序類加載器)加載 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應用程序的類加載器(也就是WebappClassLoader)中加載類。Web應用程序的類加載器是// 一個特殊的類加載器,它負責從Web應用程序的本地庫中加載類 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 } // 經過上面幾個步驟還未加載到類,則采用系統類加載器(也稱應用程序類加載器)進行加載 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} }}// 最終,還未加載到類,報類未找到的異常throw new ClassNotFoundException(name); }//...省略不需要關注的代碼}綜上所述,我們得出WebappClassLoader類加載器打破了雙親委派機制,自定義類加載類的順序:
擴展類加載器(ExtClassLoader)加載Web應用程序類加載器(WebappClassLoader)系統類加載器類(AppClassLoader)公共類加載器類(CommonClassLoader)如果Web應用程序類加載器配置為,,也就是WebappClassLoaderBase類的變量delegate=true時,則類加載順序變為:
擴展類加載器(ExtClassLoader)加載系統類加載器類(AppClassLoader)公共類加載器類(CommonClassLoader)Web應用程序類加載器(WebappClassLoader)JSP類加載器(JasperLoader)上代碼:
public class JasperLoader extends URLClassLoader { private final PermissionCollection permissionCollection; private final SecurityManager securityManager; // JSP類加載器的父加載器是Web應用程序類加載器(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;}// 當使用SecurityManager安全管理器時,允許訪問訪類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包名開頭的,則采用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包名開頭JSP類,就調用父類URLClassLoader的findClass方法// 動態加載類文件,解析成Class類,返回給調用方return findClass(name); }}下面是URLClassLoader的findClass方法,具體實現:
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 { // 解析類的字節碼文件生成Class類對象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類加載器加載的類)加載,如果不是核心類庫的類,就從Web應用程序類加載器WebappClassLoader中加載,如果還未找到,就說明是jsp類,則通過動態解析jsp類文件獲得要加載的類。
經過上面兩個Tomcat核心類加載器的剖析,我們也就知道了Tomcat類的加載原理了。
下面我們來總結一下:Tomcat會為每個Web應用程序創建一個WebappClassLoader類加載器進行類的加載,不同的類加載器實例加載的類是會被認為是不同的類,即使它們的類名相同,這樣的話就可以實現在同一個JVM下,允許Tomcat容器的不同部分以及在容器上運行的不同Web應用程序可以訪問的各種不同版本的類庫。
針對JSP類,會由專門的JSP類加載器(JasperLoader)進行加載,該加載器會針對JSP類在每次加載時都會解析類文件,Tomcat容器會啟動一個后臺線程,定時檢測JSP類文件的變化,及時更新類文件,這樣就實現JSP文件的熱加載功能。
以上就是源碼剖析Tomcat類的加載原理的詳細內容,更多關于Tomcat類加載的資料請關注好吧啦網其它相關文章!