一. tomcat是如何打破雙親委派機制的?
首先, 來舉個例子, 通常,一個tomcat要加載幾個應用程序呢? 當然是n多個應用程序, 加入我們使用的都是spring的框架, 那我們能保證所有的應用程序都是用spring4 或者spring5 么? 不可能, 他可能既有spring4的項目, 又有spring5的項目. 那么tomcat在加載spring4項目的war包是, 會不會和spring5項目的war包沖突呢? 因為spring4, 和 spring5中有很多類的類名是一樣的, 但是實現不一樣. 如果都是交給父類加載器加載, 那么肯定只能加載一份. 也就是spring4和spring5的項目不能共存. 而實際上的情況呢? 我們的tomcat可以加在各種各樣類型的war包, 相互之間沒有影響. 他是怎么做到的呢?
因為tomcat打破了雙親委派機制, 下面我們就來看看tomcat是如何打破雙親委派機制的?
如上圖, 上面的橙色部門還是和原來一樣, 采用雙親委派機制. 而黃色部分是tomcat第一部分自定義的類加載器, 這部分主要是加載tomcat包中的類, 這一部分依然采用的是雙親委派機制, 而綠色部分是tomcat第二部分自定義類加載器, 正事這一部分, 打破了類的雙親委派機制. 先面我們就來詳細看看tomcat自定的類加載器
1. tomcat第一部分自定義類加載器(黃色部分)
這部分類加載器, 在tomcat7及以前是tomcat自定義的三個類加載器, 分別在家不同文件加載的jar包. 而到了tomcat8及以后, tomcat將這三個文件夾合並了, 合並成了一個lib包. 也就是我們現在看到的lib包
我們來看看這三個類加載器的主要功能.
- commonClassLoader: tomcat最基本的類加載器, 加載路徑中的class可以被tomcat容器本身和各個webapp訪問;
- catalinaClassLoader: tomcat容器中私有的類加載器, 加載路徑中的class對於webapp不可見
- sharedClassLoader: 各個webapps共享的類加載器, 加載路徑中的class對於所有的webapp都可見, 但是對於tomcat容器不可見.
這一部分類加載器, 依然采用的是雙親委派機制, 原因是, 他只有一份. 如果有重復, 那么也是以這一份為准.
2. tomcat第二部分自定義類加載器(綠色部分)
綠色部分是java項目在打war包的時候, tomcat自動生成的類加載器, 也就是說 , 每一個項目打成一個war包, tomcat都會自動生成一個類加載器, 專門用來加載這個war包. 而這個類加載器打破了雙親委派機制. 我們可以想象一下, 加入這個webapp類加載器沒有打破雙親委派機制會怎么樣?
如果沒有打破, 他就會委托父類加載器去加載, 一旦加載到了, 子類加載器就沒有機會在加載了. 那么, spring4和spring5的項目想共存, 那是不可能的了.
所以, 這一部分他打破了雙親委派機制
這樣一來, webapp類加載器不需要在讓上級去加載, 他自己就可以加載對應war里的class文件. 當然了, 其他的項目文件, 還是要委托上級加載的.
下面我們來實現一個自定義的類加載器
二. 自定義tomcat的war包類加載器
如何打破雙親委派機制, 我們已經寫過一個demo了. 詳見: https://www.cnblogs.com/ITPower/p/13211490.html
那么, 現在我有兩個war包, 分處於不同的文件夾, tomcat如何使用各自的類加載器加載自己包下的class類呢?
我們來舉個例子, 比如: 在我的home目錄下有兩個文件夾, tomcat-test和tomcat-test1. 用這兩個文件夾來模擬兩個項目.
在他們的下面都有一個com/lxl/jvm/User1.class
雖然類名和類路徑都是一樣的,但是他們的內容是不同的
這個時候,如果tomcat要同時加載這兩個目錄下的User1.class文件, 我們如何操作呢?
其實,非常簡單, 按照上面的思路, tomcat只需要為每一個文件夾生成一個新的類加載器就可以了.
public static void main(String[] args) throws Exception {
// 第一個類加載器 DefinedClassLoaderTest classLoader = new DefinedClassLoaderTest("/Users/luoxiaoli/tomcat-test"); Class<?> clazz = classLoader.loadClass("com.lxl.jvm.User1"); Object obj = clazz.newInstance(); Method sout = clazz.getDeclaredMethod("sout", null); sout.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); // 第二個類加載器 DefinedClassLoaderTest classLoader1 = new DefinedClassLoaderTest("/Users/luoxiaoli/tomcat-test1"); Class<?> clazz1 = classLoader1.loadClass("com.lxl.jvm.User1"); Object obj1 = clazz1.newInstance(); Method sout1 = clazz1.getDeclaredMethod("sout", null); sout1.invoke(obj1, null); System.out.println(clazz1.getClassLoader().getClass().getName()); }
他們都是只加載自己目錄下的文件. 我們來看看執行結果:
調用了user1的sout方法
com.lxl.jvm.DefinedClassLoaderTest
調用了另外一個項目user1的sout方法, 他們是不同的
com.lxl.jvm.DefinedClassLoaderTest
三. 延伸思考: 我們看到上面tomcat自定義的類加載器中, 還有一個jsp類加載器. jsp是可以實現熱部署的, 那么他是如何實現的呢?
我們都知道jsp其實是一個servlet容器, 有tomcat加載. tomcat會為每一個jsp生成一個類加載器. 這樣每個類加載器都加載自己的jsp, 不會加載別人的. 當jsp文件內容修改時, tomcat會有一個監聽程序來監聽jsp的改動. 比如文件夾的修改時間, 一旦時間變了, 就重新加載文件夾中的內容.
具體tomcat是怎么實現的呢? tomcat自定義了一個thread, 用來監聽不同文件夾中文件的內容是否修改, 如何監聽呢? 就看文件夾的update time有沒有變化, 如果有變化了, 那么就會重新加載.