生活随笔
收集整理的這篇文章主要介紹了
Tomcat源码解析六:Tomcat类加载器机制
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
要說(shuō)Tomcat的Classloader機(jī)制,我們還得從Bootstrap開(kāi)始。在BootStrap初始化的時(shí)候,調(diào)用了org.apache.catalina.startup.Bootstrap#initClassLoaders方法,這個(gè)方法里面創(chuàng)建了3個(gè)ClassLoader,它們分別是commonLoader,catalinaLoader,sharedLoader,其中catalinaLoader,sharedLoader的父親加載器是commonLoader,initClassLoaders執(zhí)行的過(guò)程中會(huì)執(zhí)行createClassLoader,而createClassLoader是根據(jù)conf/catalina.properties文件中common.loader,server.loader,shared.loader的值來(lái)初始化,它的代碼如下:
[java]?view plaincopy
org.apache.catalina.startup.Bootstrap#createClassLoader??
[java]?view plaincopy
rivate?ClassLoader?createClassLoader(String?name,?ClassLoader?parent)??????throws?Exception?{????????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;??????????}??????????????????????try?{??????????????@SuppressWarnings("unused")??????????????URL?url?=?new?URL(repository);??????????????repositories.add(??????????????????????new?Repository(repository,?RepositoryType.URL));??????????????continue;??????????}?catch?(MalformedURLException?e)?{????????????????????????}??????????????????????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));??????????}??????}????????????ClassLoader?classLoader?=?ClassLoaderFactory.createClassLoader??????????(repositories,?parent);??????????return?classLoader;????}??
以上代碼刪除了與本篇無(wú)關(guān)的代碼,下面我們分別來(lái)分析一下標(biāo)注的地方:
標(biāo)注1的代碼(第5行)判斷如果catalina.properties中沒(méi)有配置對(duì)應(yīng)的loader屬性的話,直接返回父加載器,而默認(rèn)情況下,server.loader,shared.loader為空,那么此時(shí)的catalinaLoader,sharedLoader其實(shí)是同一個(gè)ClassLoader.標(biāo)注2(第9行)的地方根據(jù)環(huán)境變量的配置替換字符串中的值.默認(rèn)情況下,common.loader的值為common.loader=${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar,這里會(huì)將catalina.base和catalina.home用環(huán)境變量的值替換。標(biāo)注3(第46行)的代碼最終調(diào)用org.apache.catalina.startup.ClassLoaderFactory#createClassLoader靜態(tài)工廠方法創(chuàng)建了URLClassloader的實(shí)例,而具體的URL其實(shí)就是*.loader屬性配置的內(nèi)容,此外如果parent為null的話,則直接用系統(tǒng)類加載器。
上面分析了Tomcat在啟動(dòng)的時(shí)候,初始化的幾個(gè)ClassLoader,接下來(lái)我們?cè)賮?lái)繼續(xù)看看,這些ClassLoader具體都用在什么地方。
我們接著來(lái)看org.apache.catalina.startup.Bootstrap#init方法,在初始化完3個(gè)classLoader以后,接下來(lái)首先通過(guò)catalinaLoader加載了org.apache.catalina.startup.Catalinal類,然后通過(guò)放射調(diào)用了org.apache.catalina.startup.Catalina#setParentClassLoader,具體代碼片段如下:
[java]?view plaincopy
org.apache.catalina.startup.Bootstrap#init??
[java]?view plaincopy
Class<?>?startupClass?=??????catalinaLoader.loadClass??????("org.apache.catalina.startup.Catalina");??Object?startupInstance?=?startupClass.newInstance();????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);??
通過(guò)上面的代碼,我們可以清楚的看到調(diào)用了Catalina的setParentClassLoader放,那么到這里我們可能又要想知道,設(shè)置了parentClassLoader以后,sharedLoader又是在哪里使用的呢?這就需要我們接著來(lái)分析容器啟動(dòng)的代碼。我們通過(guò)查看
org.apache.catalina.startup.Catalina#getParentClassLoader
調(diào)用棧,我們看到在StandardContext的startInternal方法中調(diào)用了它,那么我們查看一下它的代碼,包含了如下代碼片段:
[java]?view plaincopy
org.apache.catalina.core.StandardContext#startInternal??
[java]?view plaincopy
if?(getLoader()?==?null)?{??????????????WebappLoader?webappLoader?=?new?WebappLoader(getParentClassLoader());??????????????webappLoader.setDelegate(getDelegate());??????????????setLoader(webappLoader);??}??try?{????????if?(ok)?{??????????????????????if?((loader?!=?null)?&&?(loader?instanceof?Lifecycle))??????????????((Lifecycle)?loader).start();????????????????}??catch(Exception?e){??}??
通過(guò)查看上面的代碼,我們看到在StandardContext啟動(dòng)的時(shí)候,會(huì)創(chuàng)建webapploader,創(chuàng)建webapploader的時(shí)候會(huì)將getParentClassLoader方法返回的結(jié)果(這里返回的其實(shí)就是sharedLoader)賦值給自己的parentClassLoader變量,接著又會(huì)調(diào)用到Webapploader的start方法,因?yàn)閃ebappLoader符合Tomcat組件生命周期管理的模板方法模式,因此會(huì)調(diào)用到它的startInternal方法。我們接下來(lái)就來(lái)看看WebappLoader的startInternal,我們摘取一部分與本篇相關(guān)的代碼片段如下:
[java]?view plaincopy
org.apache.catalina.loader.WebappLoader#startInternal??
[java]?view plaincopy
classLoader?=?createClassLoader();??classLoader.setResources(container.getResources());??classLoader.setDelegate(this.delegate);??classLoader.setSearchExternalFirst(searchExternalFirst);??
從上的代碼可以看到調(diào)用了createClassLoader方法創(chuàng)建一個(gè)classLoader,那么我們?cè)倏磥?lái)看看createClassLoader的代碼:
[java]?view plaincopy
org.apache.catalina.loader.WebappLoader#createClassLoader??
[java]?view plaincopy
private?WebappClassLoader?createClassLoader()??????throws?Exception?{????????Class<?>?clazz?=?Class.forName(loaderClass);??????WebappClassLoader?classLoader?=?null;????????if?(parentClassLoader?==?null)?{??????????parentClassLoader?=?container.getParentClassLoader();??????}??????Class<?>[]?argTypes?=?{?ClassLoader.class?};??????Object[]?args?=?{?parentClassLoader?};??????Constructor<?>?constr?=?clazz.getConstructor(argTypes);??????classLoader?=?(WebappClassLoader)?constr.newInstance(args);????????return?classLoader;????}??
在上面的代碼里面,loaderClass是WebappLoader的實(shí)例變量,其值為org.apache.catalina.loader.WebappClassLoader,那么上面的代碼其實(shí)就是通過(guò)反射調(diào)用了WebappClassLoader的構(gòu)造函數(shù),然后傳遞了sharedLoader作為其父親加載器。
代碼閱讀到這里,我們已經(jīng)基本清楚了Tomcat中ClassLoader的總體結(jié)構(gòu),總結(jié)如下: 在Tomcat存在common,cataina,shared三個(gè)公共的classloader,默認(rèn)情況下,這三個(gè)classloader其實(shí)是同一個(gè),都是common classloader,而針對(duì)每個(gè)webapp,也就是context(對(duì)應(yīng)代碼中的StandardContext類),都有自己的WebappClassLoader來(lái)加載每個(gè)應(yīng)用自己的類。上面的描述,我們可以通過(guò)下圖形象化的描述:
清楚了Tomcat總體的ClassLoader結(jié)構(gòu)以后,咋們就來(lái)進(jìn)一步來(lái)分析一下WebAppClassLoader的代碼,我們知道Java的ClassLoader機(jī)制有parent-first的機(jī)制,而這種機(jī)制是在loadClass方法保證的,一般情況下,我們只需要重寫findClass方法就好了,而對(duì)于WebAppClassLoader,通過(guò)查看源代碼,我們發(fā)現(xiàn)loadClass和findClass方法都進(jìn)行了重寫,那么我們首先就來(lái)看看它的loadClass方法,它的代碼如下:
org.apache.catalina.loader.WebappClassLoader#loadClass
[java]?view plaincopy
public?synchronized?Class<?>?loadClass(String?name,?boolean?resolve)??????throws?ClassNotFoundException?{????????if?(log.isDebugEnabled())??????????log.debug("loadClass("?+?name?+?",?"?+?resolve?+?")");??????Class<?>?clazz?=?null;??????????????if?(!started)?{??????????try?{??????????????throw?new?IllegalStateException();??????????}?catch?(IllegalStateException?e)?{??????????????log.info(sm.getString("webappClassLoader.stopped",?name),?e);??????????}??????}????????????????????clazz?=?findLoadedClass0(name);??????if?(clazz?!=?null)?{??????????if?(log.isDebugEnabled())??????????????log.debug("??Returning?class?from?cache");??????????if?(resolve)??????????????resolveClass(clazz);??????????return?(clazz);??????}????????????????????clazz?=?findLoadedClass(name);??????if?(clazz?!=?null)?{??????????if?(log.isDebugEnabled())??????????????log.debug("??Returning?class?from?cache");??????????if?(resolve)??????????????resolveClass(clazz);??????????return?(clazz);??????}??????????????????????????try?{??????????clazz?=?system.loadClass(name);??????????if?(clazz?!=?null)?{??????????????if?(resolve)??????????????????resolveClass(clazz);??????????????return?(clazz);??????????}??????}?catch?(ClassNotFoundException?e)?{????????????????}??????????????if?(securityManager?!=?null)?{??????????int?i?=?name.lastIndexOf('.');??????????if?(i?>=?0)?{??????????????try?{??????????????????securityManager.checkPackageAccess(name.substring(0,i));??????????????}?catch?(SecurityException?se)?{??????????????????String?error?=?"Security?Violation,?attempt?to?use?"?+??????????????????????"Restricted?Class:?"?+?name;??????????????????log.info(error,?se);??????????????????throw?new?ClassNotFoundException(error,?se);??????????????}??????????}??????}??????????????boolean?delegateLoad?=?delegate?||?filter(name);????????????????????if?(delegateLoad)?{??????????if?(log.isDebugEnabled())??????????????log.debug("??Delegating?to?parent?classloader1?"?+?parent);??????????ClassLoader?loader?=?parent;??????????if?(loader?==?null)??????????????loader?=?system;??????????try?{??????????????clazz?=?Class.forName(name,?false,?loader);??????????????if?(clazz?!=?null)?{??????????????????if?(log.isDebugEnabled())??????????????????????log.debug("??Loading?class?from?parent");??????????????????if?(resolve)??????????????????????resolveClass(clazz);??????????????????return?(clazz);??????????????}??????????}?catch?(ClassNotFoundException?e)?{????????????????????????}??????}??????????????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)?{????????????????}????????????????????if?(!delegateLoad)?{??????????if?(log.isDebugEnabled())??????????????log.debug("??Delegating?to?parent?classloader?at?end:?"?+?parent);??????????ClassLoader?loader?=?parent;??????????if?(loader?==?null)??????????????loader?=?system;??????????try?{??????????????clazz?=?Class.forName(name,?false,?loader);??????????????if?(clazz?!=?null)?{??????????????????if?(log.isDebugEnabled())??????????????????????log.debug("??Loading?class?from?parent");??????????????????if?(resolve)??????????????????????resolveClass(clazz);??????????????????return?(clazz);??????????????}??????????}?catch?(ClassNotFoundException?e)?{????????????????????????}??????}????????throw?new?ClassNotFoundException(name);????}??
我們一步步的來(lái)分析一下上面的代碼做了什么事情。
標(biāo)注1(第18行)代碼,首先從當(dāng)前ClassLoader的本地緩存中加載類,如果找到則返回。標(biāo)注2(第29行)代碼,在本地緩存沒(méi)有的情況下,調(diào)用ClassLoader的findLoadedClass方法查看jvm是否已經(jīng)加載過(guò)此類,如果已經(jīng)加載則直接返回。標(biāo)注3(第41行)代碼,通過(guò)系統(tǒng)的來(lái)加載器加載此類,這里防止應(yīng)用寫的類覆蓋了J2SE的類,這句代碼非常關(guān)鍵,如果不寫的話,就會(huì)造成你自己寫的類有可能會(huì)把J2SE的類給替換調(diào),另外假如你寫了一個(gè)javax.servlet.Servlet類,放在當(dāng)前應(yīng)用的WEB-INF/class中,如果沒(méi)有此句代碼的保證,那么你自己寫的類就會(huì)替換到Tomcat容器Lib中包含的類。標(biāo)注4(第68行)代碼,判斷是否需要委托給父類加載器進(jìn)行加載,delegate屬性默認(rèn)為false,那么delegatedLoad的值就取決于filter的返回值了,filter方法中根據(jù)包名來(lái)判斷是否需要進(jìn)行委托加載,默認(rèn)情況下會(huì)返回false.因此delegatedLoad為false標(biāo)注5(第72行)代碼,因?yàn)閐elegatedLoad為false,那么此時(shí)不會(huì)委托父加載器去加載,這里其實(shí)是沒(méi)有遵循parent-first的加載機(jī)制。標(biāo)注6(第96行)調(diào)用findClass方法在webapp級(jí)別進(jìn)行加載標(biāo)注7(第111行)如果還是沒(méi)有加載到類,并且不采用委托機(jī)制的話,則通過(guò)父類加載器去加載。
通過(guò)上面的描述,我們可以知道Tomcat在加載webapp級(jí)別的類的時(shí)候,默認(rèn)是不遵守parent-first的,這樣做的好處是更好的實(shí)現(xiàn)了應(yīng)用的隔離,但是壞處就是加大了內(nèi)存浪費(fèi),同樣的類庫(kù)要在不同的app中都要加載一份。
上面分析完了loadClass,我們接著在來(lái)分析一下findClass,通過(guò)分析findClass的代碼,最終會(huì)調(diào)用org.apache.catalina.loader.WebappClassLoader#findClassInternal方法,那我們就來(lái)分析一下它的代碼:
[java]?view plaincopy
org.apache.catalina.loader.WebappClassLoader#findClassInternal??
[java]?view plaincopy
protected?Class<?>?findClassInternal(String?name)??????throws?ClassNotFoundException?{??????????????if?(!validate(name))??????????throw?new?ClassNotFoundException(name);????????String?tempPath?=?name.replace('.',?'/');??????String?classPath?=?tempPath?+?".class";????????ResourceEntry?entry?=?null;????????if?(securityManager?!=?null)?{??????????PrivilegedAction<ResourceEntry>?dp?=??????????????new?PrivilegedFindResourceByName(name,?classPath);??????????entry?=?AccessController.doPrivileged(dp);??????}?else?{????????????????????entry?=?findResourceInternal(name,?classPath);??????}????????if?(entry?==?null)??????????throw?new?ClassNotFoundException(name);????????Class<?>?clazz?=?entry.loadedClass;??????if?(clazz?!=?null)??????????return?clazz;????????synchronized?(this)?{??????????clazz?=?entry.loadedClass;??????????if?(clazz?!=?null)??????????????return?clazz;????????????if?(entry.binaryContent?==?null)??????????????throw?new?ClassNotFoundException(name);????????????try?{????????????????????????????clazz?=?defineClass(name,?entry.binaryContent,?0,??????????????????????entry.binaryContent.length,??????????????????????new?CodeSource(entry.codeBase,?entry.certificates));??????????}?catch?(UnsupportedClassVersionError?ucve)?{??????????????throw?new?UnsupportedClassVersionError(??????????????????????ucve.getLocalizedMessage()?+?"?"?+??????????????????????sm.getString("webappClassLoader.wrongVersion",??????????????????????????????name));??????????}??????????entry.loadedClass?=?clazz;??????????entry.binaryContent?=?null;??????????entry.source?=?null;??????????entry.codeBase?=?null;??????????entry.manifest?=?null;??????????entry.certificates?=?null;??????}????????return?clazz;????}??
上面的代碼標(biāo)注1(第19行)的地方通過(guò)名稱去當(dāng)前webappClassLoader的倉(cāng)庫(kù)中查找對(duì)應(yīng)的類文件,標(biāo)注2(第38行)的代碼,將找到的類文件通過(guò)defineClass轉(zhuǎn)變?yōu)镴vm可以識(shí)別的Class對(duì)象返回。
總結(jié)
以上是生活随笔為你收集整理的Tomcat源码解析六:Tomcat类加载器机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。