违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制
轉(zhuǎn)載自?違反ClassLoader雙親委派機(jī)制三部曲第二部——Tomcat類加載機(jī)制
前言:
 本文是基于 ClassLoader雙親委派機(jī)制源碼分析 了解過正統(tǒng)JDK類加載機(jī)制及其實(shí)現(xiàn)原理的基礎(chǔ)上,進(jìn)而分析這種思想如何應(yīng)用到Tomcat這個web容器中,從源碼的角度對 違反ClassLoader雙親委派機(jī)制三部曲之首部——JDBC驅(qū)動加載 中提出的Tomcat是如何完成多個web應(yīng)用之間相互隔離,又如何保證多個web應(yīng)用都能加載到基礎(chǔ)類庫的問題進(jìn)行了解答,我們按如下的思路布局整篇文章:
- 先給出Tomcat整體的類加載體系結(jié)構(gòu)
- 通過查看源碼驗(yàn)證該類加載體系的正確性
- 總結(jié)Tomcat如何設(shè)計保證多應(yīng)用隔離
 另外本文是基于Tomcat7的源碼進(jìn)行分析的,因此讀者最好先搭建一套基于Tomcat7的環(huán)境,以便查閱源碼以及運(yùn)行調(diào)試,可以按照該文章的方式進(jìn)行搭建:Tomcat源碼導(dǎo)入Idea
Tomcat類加載體系結(jié)構(gòu)
圖1. Tomcat整體類加載體系結(jié)構(gòu)
Tomcat本身也是一個java項(xiàng)目,因此其也需要被JDK的類加載機(jī)制加載,也就必然存在引導(dǎo)類加載器、擴(kuò)展類加載器和應(yīng)用(系統(tǒng))類加載器。Tomcat自身定義的類加載器主要由圖中下半部分組成,Common ClassLoader作為Catalina ClassLoader和Shared ClassLoader的parent,而Shared ClassLoader又可能存在多個children類加載器WebApp ClassLoader,一個WebApp ClassLoader實(shí)際上就對應(yīng)一個Web應(yīng)用,那Web應(yīng)用就有可能存在Jsp頁面,這些Jsp頁面最終會轉(zhuǎn)成class類被加載,因此也需要一個Jsp的類加載器,就是圖中的JasperLoder
 需要注意的是,在代碼層面Catalina ClassLoader、Shared ClassLoader、Common ClassLoader對應(yīng)的實(shí)體類實(shí)際上都是URLClassLoader或者SecureClassLoader,一般我們只是根據(jù)加載內(nèi)容的不同和加載父子順序的關(guān)系,在邏輯上劃分為這三個類加載器;而WebApp ClassLoader和JasperLoader都是存在對應(yīng)的類加載器類的
 下面我們從源碼設(shè)計的角度驗(yàn)證圖中類加載器的設(shè)計
源碼分析Tomcat類加載機(jī)制
Tomcat的啟動入口在Bootstrap.class中
?
圖2. Tomcat啟動入口
其中初始化類加載器的流程在bootstrap.init();中,如下“代碼清單1”
public void init()throws Exception{// Set Catalina pathsetCatalinaHome();setCatalinaBase();// (1) 初始化 classLoaderinitClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif (log.isDebugEnabled())log.debug("Loading startup class");//加載 org.apache.catalina.startup.Catalina classClass<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");// (2) 實(shí)例化 Catalina 實(shí)例Object startupInstance = startupClass.newInstance();// Set the shared extensions class loaderif (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);catalinaDaemon = startupInstance;}(1)處注釋的代碼主要進(jìn)行類加載的初始化以及形成類加載器的關(guān)系初始化,繼續(xù)跟進(jìn)
圖3. initClassLoaders()方法
這里紅線處的代碼實(shí)際上創(chuàng)建了三個ClassLoader對象,其名稱和Tomcat類加載關(guān)系圖中的類加載器高度一致,那么我們猜測createClassLoader(String,ClassLoader)方法可能就是創(chuàng)建Tomcat自定義類加載器的方法之一,繼續(xù)往下看 “ 代碼清單2 ”
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {// (1) 根據(jù)名稱查找特定的配置String value = CatalinaProperties.getProperty(name + ".loader");if ((value == null) || (value.equals("")))return parent;value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}// Local repositoryif (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));}}// (2) 類加載器工廠創(chuàng)建特定類加載器return ClassLoaderFactory.createClassLoader(repositories, parent);}代碼清單中(1)處注釋是根據(jù)上圖中傳遞的“名稱”加上后綴.loader去某個配置文件加載文件,為了突出重點(diǎn),這里直接給出結(jié)論,其加載的內(nèi)容為/org/apache/catalina/startup/catalina.properties,比如要加載 common.loader對應(yīng)的value,其在文件中的值為${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,也就是說Common ClassLoader要加載的路徑是這些,是Tomcat運(yùn)行要使用的公共組件,比如servlet-api.jar、catalina.jar等;而我們發(fā)現(xiàn)當(dāng)要加載server.loader和shared.loader時,其key在配置文件中的value為空,也就是說,默認(rèn)情況下Catalina ClassLoader和Shared ClassLoader(Tomcat整體類加載體系結(jié)構(gòu)圖中紅色虛線內(nèi))都不存在,只有Common ClassLoader
 方法中的第二個參數(shù)表示創(chuàng)建類加載器的父類加載器是哪個,再看initClassLoaders()方法圖中代碼,在創(chuàng)建catalinaLoader和sharedLoader時,父類加載器傳入的實(shí)際上就是commonLoader,以此可以驗(yàn)證圖1中Catalina ClassLoader、Shared ClassLoader和Common ClassLoader的父子關(guān)系。而common ClassLoader的父類加載器參數(shù)傳遞的為null,為什么null就會導(dǎo)致該類加載器的父類加載器為System ClassLoader呢?我們需要進(jìn)入代碼清單2中看注釋(2)處標(biāo)識的代碼 代碼清單3
大塊的if中的代碼實(shí)際上是對資源進(jìn)行轉(zhuǎn)化加載的過程,而return部分才是返回類加載器的部分,代碼根據(jù)是否有parent調(diào)用了URLClassLoader不同的構(gòu)造器,Common ClassLoader調(diào)用的是沒有parent的構(gòu)造器
圖4. Common ClassLoader的parent創(chuàng)建的底層關(guān)鍵代碼
按紅線所畫Common ClassLoader的parent實(shí)際上是JDK中sun.misc.Launcher.class類的loader成員變量,而在上一篇文章中已經(jīng)知道該loader的值就是應(yīng)用類加載器(系統(tǒng)類加載器)System ClassLoader。至此Tomcat中類加載機(jī)制和JDK的類加載機(jī)制也建立上了聯(lián)系
 現(xiàn)在Tomcat的類加載機(jī)制已完成了一大半,剩下用于加載每個web應(yīng)用的類加載器WebApp ClassLoader的分析,這個時候需要重新回到代碼清單1中看注釋(2)以下的部分,其主要做的事情是通過反射創(chuàng)建了org.apache.catalina.startup.Catalina類的實(shí)例,然后調(diào)用了簽名為void setParentClassLoader(ClassLoader parentClassLoader)的方法,并傳入了Shared ClassLoader,上面我們說過默認(rèn)情況下Shared ClassLoader就是Common ClassLoader,因此其傳入的參數(shù)實(shí)際上是Common ClassLoader
 我們思考既然有保存parent的方法,必定使用時會調(diào)用獲得parent方法,那么我們需要查看Catalina類中ClassLoader getParentClassLoader()方法的調(diào)用棧(層級關(guān)系比較復(fù)雜,要緊跟主線不要迷失),最終定位到StandardContext中的synchronized void startInternal() throws LifecycleException方法(其中涉及到Tomcat的各種組件關(guān)系,生命周期管理等內(nèi)容,將在下次分析Tomcat組件文章中詳細(xì)介紹),下面是只保留核心邏輯的startInternal()方法 代碼清單4
(1)處注釋下的代碼邏輯就是為每一個web應(yīng)用創(chuàng)建一個類加載器,該類加載器的父類加載器就是通過getParentClassLoader()得到的Shared ClassLoader(Common ClassLoader),(2)處代碼調(diào)用了WebappLoader的start方法,繼續(xù)跟進(jìn)
protected void startInternal() throws LifecycleException {// 其他邏輯省略.....try {//創(chuàng)建類加載器關(guān)鍵方法classLoader = createClassLoader();classLoader.setResources(container.getResources());classLoader.setDelegate(this.delegate);classLoader.setSearchExternalFirst(searchExternalFirst);if (container instanceof StandardContext) {classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());classLoader.setClearReferencesRmiTargets(((StandardContext) container).getClearReferencesRmiTargets());classLoader.setClearReferencesStatic(((StandardContext) container).getClearReferencesStatic());classLoader.setClearReferencesStopThreads(((StandardContext) container).getClearReferencesStopThreads());classLoader.setClearReferencesStopTimerThreads(((StandardContext) container).getClearReferencesStopTimerThreads());classLoader.setClearReferencesHttpClientKeepAliveThread(((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());}// 其他邏輯省略.....}由于Tomcat的設(shè)計,WebappLoader的start方法實(shí)際上調(diào)用的是父類的模板,而模板中的startinternal方法由各個子類具體實(shí)現(xiàn),其中最關(guān)鍵的方法為createClassLoader()
圖5. WebappLoader中createClassLoader方法
上圖中的loadClass成員變量的值為org.apache.catalina.loader.WebappClassLoader,所以,實(shí)際上該類為每一個web應(yīng)用創(chuàng)建了一個WebappClassLoader的實(shí)例,該實(shí)例的parent就是Shared ClassLoader或者Common ClassLoader,至此WebApp ClassLoader在圖1中的位置也得以驗(yàn)證。
 從理論上分析來看,由于類加載的“雙親委派”機(jī)制,一個類加載器只能加載本加載器指定的目錄以及使用有“繼承”關(guān)系的父類加載器加載過的類,而Tomcat為每一個Web應(yīng)用創(chuàng)建了一個WebappClassLoader,不同的WebappClassLoader是同級關(guān)系,不會存在交叉訪問的問題,從而達(dá)到web應(yīng)用相互隔離的目的。
 那Tomcat是否沒有"破壞"雙親委派機(jī)制呢?我們通過查看WebappClassLoader及其父類WebappClassLoaderBase的loadClass()和findClass()分析一下Tomcat加載web應(yīng)用相關(guān)類的策略
我們首先定位到WebappClassLoaderBase的loadClass方法,(1)處首先看name對應(yīng)的類是否存在緩存中,緩存是一個ConcurrentHashMap<String, ResourceEntry>的集合,如果沒有緩存執(zhí)行(2)處邏輯,從JVM中查找是否曾今加載過該類,(3)中的代碼保證自定義類不會覆蓋java基礎(chǔ)類庫中的類,(4)的邏輯就是是否進(jìn)行雙親委派的分叉口,其中delegate默認(rèn)為false,那么就要看filter(String)方法,該方法的內(nèi)部實(shí)際上將待加載類的全路徑名稱和一個成員變量protected static final String[] packageTriggers中的類名進(jìn)行比較,如果待加載的類名和packageTriggers數(shù)組中的內(nèi)容前綴匹配,則需要委派父類加載,即執(zhí)行(5)處代碼,否則執(zhí)行(6),調(diào)用重寫的findClass(String)方法加載該類
public Class<?> findClass(String name) throws ClassNotFoundException {// 其他代碼略去.....// Ask our superclass to locate this class, if possible// (throws ClassNotFoundException if it is not found)Class<?> clazz = null;try {if (log.isTraceEnabled())log.trace(" findClassInternal(" + name + ")");// (1)if (hasExternalRepositories && searchExternalFirst) {try {clazz = super.findClass(name);} catch(ClassNotFoundException cnfe) {// Ignore - will search internal repositories next} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace(" -->RuntimeException Rethrown", e);throw e;}}// (2)if ((clazz == null)) {try {clazz = findClassInternal(name);} catch(ClassNotFoundException cnfe) {if (!hasExternalRepositories || searchExternalFirst) {throw cnfe;}} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace(" -->RuntimeException Rethrown", e);throw e;}}//其他代碼略去........return (clazz);}(1)處由于hasExternalRepositories和searchExternalFirst默認(rèn)為false,因此執(zhí)行(2)處邏輯,調(diào)用findClassInternal(String)方法
圖6. WebappClassLoader類的findClassInternal方法
其主要的思想是根據(jù)待加載類的全路徑讀取該類的二進(jìn)制數(shù)據(jù),進(jìn)而進(jìn)行類的預(yù)定義、class source的解析等,將該類加載到JVM中
 綜上所述,我認(rèn)為Tomcat的類加載機(jī)制不能算完全“正統(tǒng)”的雙親委派,WebappClassLoader內(nèi)部重寫了loadClass和findClass方法,實(shí)現(xiàn)了繞過“雙親委派”直接加載web應(yīng)用內(nèi)部的資源,當(dāng)然可以通過在Context.xml文件中加上<Loader delegate = "true">開啟正統(tǒng)的“雙親委派”加載機(jī)制
?
總結(jié)
以上是生活随笔為你收集整理的违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: I5电脑配置(i5配置的电脑配置)
- 下一篇: 剪辑视频电脑配置(视频 电脑配置)
