在講Tomcat的載入器之前,先要了解一下java的類加載機制,這里就不具體說了,僅僅寫一點我認為比較重要的東西:
? ? 1:一般實現自己的類加載器是重寫ClassLoader的findClass方法,然后在這個方法里面讀取class文件為byte[]數組,傳入defineClass方法,defineClass方法返回我們加載的類。這樣便實現了我們自己的簡單的類加載器。下面是一個簡單的自定義類加載器的findClass方法:
- protected ?Class<?>?findClass(String?name)? throws ?ClassNotFoundException?{???
- ???????? byte []?classData?=?getClassData(name);? //getClassData方法是通過class文件的名字得到一個字節數組 ??
- ???????? if ?(classData?==? null )?{???
- ???????????? throw ? new ?ClassNotFoundException();???
- ????????}???
- ???????? else ?{???
- ???????????? return ?defineClass(name,?classData,? 0 ,?classData.length);? //加載進jvm中。 ??
- ????????}???
- ????}???
問題是: 可不可以重寫ClassLoader的loadClass方法來實現自己的類加載器? 答案是可以,Tomcat就是用的這種方法。jdk建議我們實現自己的類加載器的時候是重寫findClass方法,不建議重寫loadclass方法,因為ClassLoader的loadclass方法保證了類加載的父親委托機制,如果重寫了這個方法,就意味著需要實現自己在重寫的loadclass方法實現父親委托機制,下面看看ClassLoader的loadclass方法,看怎么實現父親委托機制的:
- protected ? synchronized ?Class<?>?loadClass(String?name,? boolean ?resolve)??
- throws ?ClassNotFoundException??
- ???{??
- //?First,?check?if?the?class?has?already?been?loaded ??
- Class?c?=?findLoadedClass(name); //檢查該類是否已經被加載了 ??
- if ?(c?==? null )?{??
- ???? try ?{??
- ???? if ?(parent?!=? null )?{ //如果父加載器不為空就用父加載器加載該類。 ??
- ????????c?=?parent.loadClass(name,? false );??
- ????}? else ?{??
- ????????c?=?findBootstrapClass0(name);??
- ????}??
- ????}? catch ?(ClassNotFoundException?e)?{??
- ???????? //?If?still?not?found,?then?invoke?findClass?in?order ??
- ???????? //?to?find?the?class. ??
- ????????c?=?findClass(name); //在自己實現的類加載器中重寫這個findClass方法。 ??
- ????}??
- }??
- if ?(resolve)?{??
- ????resolveClass(c);??
- }??
- return ?c;??
- ???}??
2:在我們自己實現的類加載器中,defineClass方法才是真正的把類加載進jvm,defineClass是從ClassLoader繼承而來,把一個表示類的字節數組加載進jvm轉換為一個類。
3:我們自己實現的類加載器跟系統的類加載器沒有本質的區別,最大的區別就是加載的路徑不同,系統類加載器會加載環境變量CLASSPATH中指明的路徑和jvr文件,我們自己的類加載器可以定義自己的需要加載的類文件路徑.同樣的一個class文件,用系統類加載器和自定義加載器加載進jvm后類的結構是沒有區別的,只是他們訪問的權限不一樣,生成的對象因為加載器不同也會不一樣.當然我們自己的類加載器可以有更大的靈活性,比如把一個class文件(其實就是二進制文件)加密后(簡單的加密就把0和1互換),系統類加載器就不能加載,需要由我們自己定義解密類的加載器才能加載該class文件.
現在來初步的看看Tomcat的類加載器,為什么Tomcat要有自己的類加載器.這么說吧,假如沒有自己的類加載器,我們知道,在一個Tomcat中是可以部署很多應用的,如果所有的類都由系統類加載器來加載,那么部署在Tomcat上的A應用就可以訪問B應用的類,這樣A應用與B應用之間就沒有安全性可言了。還有一個原因是因為Tomcat需要實現類的自動重載,所以也需要實現自己的類加載器。Tomcat的載入器是實現了Loader接口的WebappLoader類,也是Tomcat的一個組件,實現Lifecycle接口以便統一管理,啟動時調用start方法,在start方法主要做了以下的事情:
1:創建一個真正的類加載器以及設置它加載的路徑,調用createClassLoader方法
2:啟動一個線程來支持自動重載,調用threadStart方法
看createClassLoader方法:
- private ?WebappClassLoader?createClassLoader()??
- ??????? throws ?Exception?{??
- ??
- ???????Class?clazz?=?Class.forName(loaderClass);??
- ???????WebappClassLoader?classLoader?=? null ;??
- ??
- ??????? if ?(parentClassLoader?==? null )?{??
- ??????????? //?Will?cause?a?ClassCast?is?the?class?does?not?extend?WCL,?but ??
- ??????????? //?this?is?on?purpose?(the?exception?will?be?caught?and?rethrown) ??
- ???????????classLoader?=?(WebappClassLoader)?clazz.newInstance();??
- ???????}? else ?{??
- ???????????Class[]?argTypes?=?{?ClassLoader. class ?};??
- ???????????Object[]?args?=?{?parentClassLoader?};??
- ???????????Constructor?constr?=?clazz.getConstructor(argTypes);??
- ???????????classLoader?=?(WebappClassLoader)?constr.newInstance(args);??
- ???????}??
- ??
- ??????? return ?classLoader;??
- ??
- ???}??
在createClassLoader方法內,實例化了一個真正的類加載器WebappClassLoader,代碼很簡單。WebappClassLoader是Tomcat的類加載器,它繼承了URLClassLoader(這個類是ClassLoader的孫子類)類。重寫了findClass和loadClass方法。Tomcat的findClass方法并沒有加載相關的類,只是從已經加載的類中查找這個類有沒有被加載,具體的加載是在重寫的loadClass方法中實現,從上面的對java的討論可知,重寫了loadClass方法就意味著失去了類加載器的父親委托機制,需要自己來實現父親委托機制。Tomcat正是這么做的,下面看WebappClassLoader的loadClass方法:
- public ?Class?loadClass(String?name,? boolean ?resolve)??
- ???????? throws ?ClassNotFoundException?{??
- ???????? if ?(debug?>=? 2 )??
- ????????????log( "loadClass(" ?+?name?+? ",?" ?+?resolve?+? ")" );??
- ????????Class?clazz?=? null ;??
- ??
- ???????? //?Don't?load?classes?if?class?loader?is?stopped ??
- ???????? if ?(!started)?{??
- ????????????log( "Lifecycle?error?:?CL?stopped" );??
- ???????????? throw ? new ?ClassNotFoundException(name);??
- ????????}??
- ??
- ???????? //?(0)?Check?our?previously?loaded?local?class?cache ??
- ???????? //查找本地緩存是否已經加載了該類 ??
- ????????clazz?=?findLoadedClass0(name);??
- ???????? if ?(clazz?!=? null )?{??
- ???????????? if ?(debug?>=? 3 )??
- ????????????????log( "??Returning?class?from?cache" );??
- ???????????? if ?(resolve)??
- ????????????????resolveClass(clazz);??
- ???????????? return ?(clazz);??
- ????????}??
- ??
- ???????? //?(0.1)?Check?our?previously?loaded?class?cache ??
- ????????
- ????????clazz?=?findLoadedClass(name);??
- ???????? if ?(clazz?!=? null )?{??
- ???????????? if ?(debug?>=? 3 )??
- ????????????????log( "??Returning?class?from?cache" );??
- ???????????? if ?(resolve)??
- ????????????????resolveClass(clazz);??
- ???????????? return ?(clazz);??
- ????????}??
- ??
- ???????? //?(0.2)?Try?loading?the?class?with?the?system?class?loader,?to?prevent ??
- ???????? //???????the?webapp?from?overriding?J2SE?classes ??
- ???????? try ?{??
- ????????????clazz?=?system.loadClass(name); //?(0.2)?先檢查系統ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的類定義不能覆蓋JVM?底層能夠查找到的定義(譬如不能通過定義java.lang.Integer替代底層的實現 ??
- ???????????? if ?(clazz?!=? null )?{??
- ???????????????? if ?(resolve)??
- ????????????????????resolveClass(clazz);??
- ???????????????? return ?(clazz);??
- ????????????}??
- ????????}? catch ?(ClassNotFoundException?e)?{??
- ???????????? //?Ignore ??
- ????????}??
- ??
- ???????...............................................??
- //這是一個很奇怪的定義,JVM的ClassLoader建議先由parent去load,load不到自己再去load(見如上?ClassLoader部分),而Servelet規范的建議則恰好相反,Tomcat的實現則做個折中,由用戶去決定(context的?delegate定義),默認使用Servlet規范的建議,即delegate=false ??
- ???????? boolean ?delegateLoad?=?delegate?||?filter(name);??
- //?(1)?先由parent去嘗試加載,此處的parent是SharedClassLoader,見如上說明,如上說明,除非設置了delegate,否則這里不執行 ??
- ???????? //?(1)?Delegate?to?our?parent?if?requested ??
- ???????? if ?(delegateLoad)?{??
- ???????????? if ?(debug?>=? 3 )??
- ????????????????log( "??Delegating?to?parent?classloader" );??
- ????????????ClassLoader?loader?=?parent;??
- ???????????? if ?(loader?==? null ) //此處parent是否為空取決于context?的privileged屬性配置,默認privileged=true,即parent為SharedClassLoader ??
- ????????????????loader?=?system;??
- ???????????? try ?{??
- ????????????????clazz?=?loader.loadClass(name);??
- ???????????????? if ?(clazz?!=? null )?{??
- ???????????????????? if ?(debug?>=? 3 )??
- ????????????????????????log( "??Loading?class?from?parent" );??
- ???????????????????? if ?(resolve)??
- ????????????????????????resolveClass(clazz);??
- ???????????????????? return ?(clazz);??
- ????????????????}??
- ????????????}? catch ?(ClassNotFoundException?e)?{??
- ????????????????;??
- ????????????}??
- ????????}??
- ??
- ??????? //?到WEB-INF/lib和WEB-INF/classes目錄去搜索,細節部分可以再看一下findClass,會發現默認是先搜索WEB-INF/classes后搜索WEB-INF/lib ??
- ???????? if ?(debug?>=? 3 )??
- ????????????log( "??Searching?local?repositories" );??
- ???????? try ?{??
- ????????????clazz?=?findClass(name);??
- ???????????? if ?(clazz?!=? null )?{??
- ???????????????? if ?(debug?>=? 3 )??
- ????????????????????log( "??Loading?class?from?local?repository" );??
- ???????????????? if ?(resolve)??
- ????????????????????resolveClass(clazz);??
- ???????????????? return ?(clazz);??
- ????????????}??
- ????????}? catch ?(ClassNotFoundException?e)?{??
- ????????????;??
- ????????}??
- ??
- ???????? //?(3)?Delegate?to?parent?unconditionally ??
- ???????? if ?(!delegateLoad)?{??
- ???????????? if ?(debug?>=? 3 )??
- ????????????????log( "??Delegating?to?parent?classloader" );??
- ????????????ClassLoader?loader?=?parent;??
- ???????????? if ?(loader?==? null )??
- ????????????????loader?=?system;??
- ???????????? try ?{??
- ????????????????clazz?=?loader.loadClass(name);??
- ???????????????? if ?(clazz?!=? null )?{??
- ???????????????????? if ?(debug?>=? 3 )??
- ????????????????????????log( "??Loading?class?from?parent" );??
- ???????????????????? if ?(resolve)??
- ????????????????????????resolveClass(clazz);??
- ???????????????????? return ?(clazz);??
- ????????????????}??
- ????????????}? catch ?(ClassNotFoundException?e)?{??
- ????????????????;??
- ????????????}??
- ????????}??
- ??
- ???????? //?This?class?was?not?found ??
- ???????? throw ? new ?ClassNotFoundException(name);??
- ??
- ????}??
上面的代碼很長,但實現了Tomcat自己的類加載機制,具體的加載規則是:
1:因為所有已經載入的類都會緩存起來,所以先檢查本地緩存
2:如本地緩存沒有,則檢查上一級緩存,即調用ClassLoader類的findLoadedClass()方法;
3:若兩個緩存都沒有,則使用系統的類進行加載,防止Web應用程序中的類覆蓋J2EE的類
4:若打開標志位delegate(表示是否代理給父加載器加載),或者待載入的類是屬于包觸發器的包名,則調用父類載入器來加載,如果父類載入器是null,則使用系統類載入器
5:從當前倉庫中載入相關類
6:若當前倉庫中沒有相關類,且標志位delegate為false,則調用父類載入器來加載,如果父類載入器是null,則使用系統類載入器(4跟6只能執行一個步驟的)
這里有有一點不明白的是在第三步使用系統類加載若失敗后,在第四步和第六步就沒必要但父類載入器為null的時候再用系統類載入器來載入了???有誰明白的麻煩留個言,不勝感激~
參考了一下 http://ayufox.iteye.com/blog/631190 這篇博文.
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
