小編給大家分享一下Tomcat9中類加載體系是怎么樣的,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)擁有一支富有激情的企業(yè)網站制作團隊,在互聯(lián)網網站建設行業(yè)深耕十年,專業(yè)且經驗豐富。十年網站優(yōu)化營銷經驗,我們已為成百上千中小企業(yè)提供了成都網站制作、網站設計解決方案,按需開發(fā)網站,設計滿意,售后服務無憂。所有客戶皆提供一年免費網站維護!
Tomcat作為Web容器,支持多個應用程序的部署運行,所以會涉及應用程序間類庫的隔離和共享,例如:不同應用程序既可以使用某個工具類庫的不同版本但互不影響,亦可以共享使用某個類庫。
在JVM中有3種系統(tǒng)提供的類加載器:
啟動類加載器Bootstrap ClassLoader:這負載加載存放在
擴展類加載器Extension ClassLoader:負載加載
應用程序類加載器 Application ClassLoader:負載加載用戶類路徑CLASSPATH上所指定的類庫,ClassLoader.getSysteClassLoader()方法的返回值就是此加載器,一般情況下這個就是程序中默認的類加載器
我們的應用程序都是由這3種類加載器互相配合進行加載的,還可以加入自己定義的類加載器,他們之間的關系如圖所示:
圖中展示的類加載器之間的層次關系,成為類加載器的雙親委派模型(Parents Delegation Model),雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應該有父類加載器。這里類加載器之間的父子關系一般不會繼承(Inheritance)的強耦合關系來實現(xiàn),而是使用組合(Composition)的弱耦合關系來復用父加載器的代碼。
類加載器的雙親委派模型在JDK1.2期間被引入并被廣泛應用于之后的所有Java程序中,但它并不是一個強制的約束模型,而是Java設計者推薦給開發(fā)者的一種類加載器實現(xiàn)方式,在之后章節(jié)中會看到,在Apache Tomcat中就打破了此模型。
雙親委派模型的工作過程是:如果一個類加載器收到加載類的請求,他首先不會自己去加載這個類,而是把這個請求委派給父加載器去完成,每個層次的類加載器都是如此處理,所有的加載請求最終都會傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求時(沒有找類),子加載器才會嘗試自己加載。
雙親委派模型本質上就是委托代理,雖然簡單,卻使Java類隨著類加載器一起具備了優(yōu)先級層次,例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類加載器加載這個類,最終都是委派給啟動類加載器進行加載,最終Object類在程序中的各種類加載器環(huán)境中都是同一個類。也就是說,如果你嘗試編寫一個與rJDK基礎類庫中已有類重名的Java類,類似java.lang.Object,你會發(fā)現(xiàn),雖然可以正常編譯,但永遠無法被加載運行。
功能健全的Web容器的類加載器需要支持以下幾點:
服務器的類庫要與Web應用程序類庫互相隔離,互不影響
同一個服務器上兩個Web程序所使用的Java類庫實現(xiàn)互相獨立隔離
同一個服務器上了兩個Web應用程序所使用的Java類庫可以被共享
支持JSP的熱替換(HotSwap),修改JSP文件不需要重啟服務器
基于以上需求,下圖為Tomcat類加載器的委派關系:
Common ClassLoader:Tomcat的基本類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp應用程序訪問
Catalina ClassLoader:Tomcat容器私有的類加載器,加載路徑中的class僅對Tomcat容器本身可見,Webapp應用程序不可訪問
Shared ClassLoader:各個Webapp應用程序共享的類加載器,加載路徑中的class對于所有的webapp可見
Webapp ClassLoader:各個 Webapp私有的類加載器,加載路徑中的class僅對當前webapp可見
JasperLoader:記載方位僅為JSP文件所編譯出的一個.class文件,當容器檢測到JSP文件被修改時,互替換掉目前的JasperLoader實例,通過新建一個JSP類加載器來實現(xiàn)JSP文件的HotSwap功能
/** * Initialize daemon. * @throws Exception Fatal initialization error */ public void init() throws Exception { initClassLoaders(); //初始化類加載器 Thread.currentThread().setContextClassLoader(catalinaLoader); //設置當前線程類加載器為catalinaLoader SecurityClassLoad.securityClassLoad(catalinaLoader); //使用Java SecurityManager初始化 // 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(); // 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; //WebappLoader的parentLoader Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
commLoader、catalinaLoader和sharedLoader的初始化是在Tomcat容器運行的最初進行的,調用的方法為org.apache.catalina.startup.Bootstrap.init()
initClassLoaders初始化commLoader、catalinaLoader和sharedLoader
設置當前線程的類加載器為catalinaLoader,實現(xiàn)catalinaLoader為容器自身的私有類加載的目的
當使用Java SecurityManager時進行一些初始化工作
將sharedLoader向后傳遞,以作為webappLoader的parent
private void initClassLoaders() { try { 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 = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } }
initClassLoaders方法中,對commLoader、catalinaLoader和sharedLoader進行初始化
commLoader、catalinaLoader和sharedLoader均通過createClassLoader創(chuàng)建
createClassLoader方法的第二參數為父類加載器,所以catalinaLoader、sharedLoader的父類加載器為commLoader
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; value = replace(value); Listrepositories = new ArrayList<>(); String[] repositoryPaths = getPaths(value); 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); }
獲取類加載器相應的加載路徑,路徑的配置信息默認在conf/catalina.properties中,也可以通過環(huán)境變量catalina.config指定配置文件
類加載路徑支持:.jar、*.jar和目錄三種形式
當沒有配置類加載器對應的加載路徑時,則直接返回父類加載器。Tomcat9中catalinaLoader和sharedLoader均沒有進行配置,故這兩個加載器均為commonLoader
以下為Tomat9的conf/catalina.properties默認的加載路徑配置:
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" server.loader= shared.loader=
/** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { ...省略其他代碼... if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } ...省略其他代碼... }
webappLoader的初始化在StandardContext中進行,這里創(chuàng)建WebappLoader
//重新實現(xiàn)ClassLoader private String loaderClass = ParallelWebappClassLoader.class.getName(); @Override protected void startInternal() throws LifecycleException { ...省略其他代碼... // Construct a class loader based on our current repositories list try { classLoader = createClassLoader(); //創(chuàng)建webappLoader classLoader.setResources(context.getResources()); classLoader.setDelegate(this.delegate); //是否雙親委派 ...省略其他代碼... } /** * Create associated classLoader. */ private WebappClassLoaderBase createClassLoader() throws Exception { Class> clazz = Class.forName(loaderClass); WebappClassLoaderBase classLoader = null; if (parentClassLoader == null) { parentClassLoader = context.getParentClassLoader(); } Class>[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor> constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoaderBase) constr.newInstance(args); //反射創(chuàng)建類加載器 return classLoader; }
在WebappLoader的startInternal()方法調用createClassLoader()創(chuàng)建webappLoader
createClassLoader()通過反射創(chuàng)建ParallelWebappClassLoader
public class ParallelWebappClassLoader extends WebappClassLoaderBase{ ...省略其他代碼... } public abstract class WebappClassLoaderBase extends URLClassLoader implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck { ...省略其他代碼... @Override public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) { if (log.isDebugEnabled()) log.debug("loadClass(" + name + ", " + resolve + ")"); Class> clazz = null; // Log access to stopped class loader checkStateForClassLoading(name); // (0)檢查之前加載過的本地緩存,如果緩存存在,則取緩存中的類 clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return clazz; } // (0.1)檢查之前加載過的本地緩存,如果緩存存在,則取緩存中的類 // GraalVM直接返回null,查詢緩存時,會校驗name的合法性 clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return clazz; } //(0.2) 通過系統(tǒng)加載器加載類,放置webapp中覆寫Java SE的基礎類 //此處的類加載器應為Bootstrap ClassLoader String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { URL url; if (securityManager != null) { PrivilegedActiondp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else { url = javaseLoader.getResource(resourceName); } tryLoadingFromJavaseLoader = (url != null); } catch (Throwable t) { // Swallow all exceptions apart from those that must be re-thrown ExceptionUtils.handleThrowable(t); // The getResource() trick won't work for this class. We have to // try loading it directly and accept that we might get a // ClassNotFoundException. tryLoadingFromJavaseLoader = true; } if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } } // (0.5) SecurityManager安全檢查 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); } } } boolean delegateLoad = delegate || filter(name, true); // (1) 當設置雙親委派或者加載類的名稱為Tomcat容器內部的類,則委托給父類加載器加載 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 } } // (2) 在應用類加載路徑進行加載 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 } // (3) 委托父類加載器加載 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); } ...省略其他代碼... }
ParallelWebappClassLoader繼承了WebappClassLoaderBase,而WebappClassLoaderBase繼承了URLClassLoader,并覆寫了loadClass()方法,相當于實現(xiàn)了自己的類加載器
loadClass()方法顯示,Tomcat類加載體系打破了雙親委派模型,其加載過程為
從緩存中查詢,如果已加載過,則直接返回緩存中的class
通過系統(tǒng)類加載器加載(Bootstrap ClassLoader),已避免應用程序中覆寫JavaSE基礎類
判斷是否設置為雙親委派,或者classname為特定路徑下的,則委托給父類加載器夾雜
通過應用程序類加載路徑加載,加載通過WebResourceRoot進行
當雙親委派標記為假時,最終委托給父類加載器加載
其中必須通過父類加載加載的類名稱通過filter()方法判斷:
protected boolean filter(String name, boolean isClassName) { if (name == null) return false; char ch; if (name.startsWith("javax")) { /* 5 == length("javax") */ if (name.length() == 5) { return false; } ch = name.charAt(5); if (isClassName && ch == '.') { /* 6 == length("javax.") */ if (name.startsWith("servlet.jsp.jstl.", 6)) { return false; } if (name.startsWith("el.", 6) || name.startsWith("servlet.", 6) || name.startsWith("websocket.", 6) || name.startsWith("security.auth.message.", 6)) { return true; } } else if (!isClassName && ch == '/') { /* 6 == length("javax/") */ if (name.startsWith("servlet/jsp/jstl/", 6)) { return false; } if (name.startsWith("el/", 6) || name.startsWith("servlet/", 6) || name.startsWith("websocket/", 6) || name.startsWith("security/auth/message/", 6)) { return true; } } } else if (name.startsWith("org")) { /* 3 == length("org") */ if (name.length() == 3) { return false; } ch = name.charAt(3); if (isClassName && ch == '.') { /* 4 == length("org.") */ if (name.startsWith("apache.", 4)) { /* 11 == length("org.apache.") */ if (name.startsWith("tomcat.jdbc.", 11)) { return false; } if (name.startsWith("el.", 11) || name.startsWith("catalina.", 11) || name.startsWith("jasper.", 11) || name.startsWith("juli.", 11) || name.startsWith("tomcat.", 11) || name.startsWith("naming.", 11) || name.startsWith("coyote.", 11)) { return true; } } } else if (!isClassName && ch == '/') { /* 4 == length("org/") */ if (name.startsWith("apache/", 4)) { /* 11 == length("org/apache/") */ if (name.startsWith("tomcat/jdbc/", 11)) { return false; } if (name.startsWith("el/", 11) || name.startsWith("catalina/", 11) || name.startsWith("jasper/", 11) || name.startsWith("juli/", 11) || name.startsWith("tomcat/", 11) || name.startsWith("naming/", 11) || name.startsWith("coyote/", 11)) { return true; } } } } return false; }
public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot { ...省略其他代碼... private final ListpreResources = new ArrayList<>(); private WebResourceSet main; private final List classResources = new ArrayList<>(); private final List jarResources = new ArrayList<>(); //對應WEB-INF/lib private final List postResources = new ArrayList<>(); private final List mainResources = new ArrayList<>(); //對應應用webapp路徑 private final List > allResources = new ArrayList<>(); { allResources.add(preResources); allResources.add(mainResources); allResources.add(classResources); allResources.add(jarResources); allResources.add(postResources); } ...省略其他代碼... }
StandardRoot 在容器創(chuàng)建時進行初始化,其實現(xiàn)了接口WebResourceRoot ,用于加載Webapp類庫。
以上代碼可以看出,在Tomcat中Webapp類加載順序為:
preResources->mainResources->classResources->jarResources->postResources
其中主要用到的資源路徑為:
mainResources:對應應用目錄下WEB-INF/classes
classResources:對應應用目錄下WEB-INF/lib
所以在開發(fā)時,可以在src目錄下覆蓋lib包中的類,因為WEB-INF/classes會有限WEB-INF/lib進行加載。通常我們可以將依賴的第三方類庫的源代碼復制到src目錄經,通過Tomcat運行進行debug,這對于排查第三方類庫的問題很有幫助。
public ClassLoader getJspLoader() { if( jspLoader == null ) { jspLoader = new JasperLoader (new URL[] {baseUrl}, getClassLoader(), rctxt.getPermissionCollection()); } return jspLoader; } public void clearJspLoader() { jspLoader = null; }
在Tomcat的conf/web.xml中,指定了處理JSP的Servlet:org.apache.jasper.servlet.JspServlet,在處理JSP頁面的請求時,Tomcat會檢測相應的JSP文件是否發(fā)生的修改,如果修改則會清理JSP的編譯文件,然后先生成java文件,在編譯為class文件
jspLoader為加載JSP的轉化成的Servlet的類加載器,在Tomcat檢測到jsp文件發(fā)生變化時都會重新生成
jspLoader的父類加載器為webapp的classloader,父類加載器的初始在org.apache.jasper.compiler.JspRuntimeContext的構造方法中,詳見如下代碼
public JspRuntimeContext(ServletContext context, Options options) { ..省略其他代碼 // Get the parent class loader ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) { loader = this.getClass().getClassLoader(); } parentClassLoader = loader; ...省略其他代碼... }
以上是“Tomcat9中類加載體系是怎么樣的”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道!