Tomcat - 模拟Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离
文章目錄
- Pre
- Tomcat要解決什么問題?
- Tomcat違反了雙親委派機制?
- 模擬Tomcat的webappClassLoader加載自己war包應用內不同版本類實現相互共存與隔離
- Tomcat加載機制小結
Pre
Tomcat - 都說Tomcat違背了雙親委派機制,到底對不對?
JVM-白話聊一聊JVM類加載和雙親委派機制源碼解析
JVM - 實現自定義的ClassLoader就是這么簡單
Tomcat要解決什么問題?
作為一個Web容器,Tomcat要解決什么問題 , Tomcat 如果使用默認的雙親委派類加載機制能不能行?
我們知道Tomcat可以部署多個應用,不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求同一個類庫在同一個服務器只有一份,因此要保證每個應用程序的類庫都是獨立的,保證相互隔離 .
舉個例子 假設APP1 使用的是 Spring4 , APP2 使用的是Spring5 , 毫無疑問 Spring4 和 Spring 5 肯定有 類的全路徑一樣的類吧,如果使用雙親委派 ,父加載器加載誰?
部署在同一個web容器中相同的類庫相同的版本可以共享, 比如jdk的核心jar包,否則,如果服務器有n個應用程序,那么要有n份相同的類庫加載進虛擬機。
web容器 自己依賴的類庫 (tomcat lib目錄下),不能與應用程序的類庫混淆。基于安全考慮,應該讓容器的類庫和程序的類庫隔離開來。
web容器要支持jsp的修改, jsp 文件最終也是要編譯成class文件才能在虛擬機中運行, web容器需要支持 jsp 修改后不用重啟 ,就是熱加載的功能。
結合上面的4個問題,我們看下雙親委派能不能支持?
第一個問題,如果使用默認的類加載器機制,肯定是無法加載兩個相同類庫的不同版本的,如果使用雙親委派,讓父加載器去加載 ,不管你是什么版本的,只要你的全限定類名一樣,那肯定只有一份,APP 隔離 無法滿足
第二個問題,默認的類加載器是能夠實現的,很好理解嘛, 就是雙親委派的功能,保證唯一性。
第三個問題和第一個問題一樣。
第四個問題, 要怎么實現jsp文件的熱加載呢? jsp 文件其實也就是class文件,那么如果修改了,但類名還是一樣,類加載器會直接取方法區中已經存在的,修改后的jsp是不會重新加載的。那么怎么辦呢?可以直接卸載掉這jsp文件的類加載器 .當一個jsp文件修改了,就直接卸載這個jsp類加載器。重新創建類加載器,重新加載jsp文件。 源碼詳見: org.apache.jasper.servlet.JasperLoader
Tomcat違反了雙親委派機制?
Bootstrap|System|Common/ \Webapp1 Webapp2 ...也不盡然,核心的Java的加載還是遵從雙親委派 。
Tomcat中 各個web應用自己的類加載器(WebAppClassLoader)會優先加載,打破了雙親委派機制。加載不到時再交給commonClassLoader走雙親委托 .
模擬Tomcat的webappClassLoader加載自己war包應用內不同版本類實現相互共存與隔離
我們基于JVM - 實現自定義的ClassLoader就是這么簡單
package com.gof.facadePattern;import java.io.FileInputStream; import java.lang.reflect.Method;/*** @author 小工匠* @version v1.0* @create 2020-06-11 23:09* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name+ ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {if ("com.gof.facadePattern.Boss1".equals(name)){c = findClass(name);}else{// 交由父加載器去加載c = this.getParent().loadClass(name);}}if (resolve) {resolveClass(c);}return c;}}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);//defineClass將一個字節數組轉為Class對象,這個字節數組是class文件讀取后最終的字節數組。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}public static void main(String args[]) throws Exception {//初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載器設置為應用程序類加載器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:/artisan");//D盤創建 artisan/com/gof/facadePattern 目錄,將Boss類的復制類Boss1.class丟入該目錄Class clazz = classLoader.loadClass("com.gof.facadePattern.Boss1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader() );System.out.println();MyClassLoader classLoader1 = new MyClassLoader("D:/artisan1");//D盤創建 artisan1/com/gof/facadePattern 目錄,將Boss類的復制類Boss1.class丟入該目錄Class clazz1 = classLoader1.loadClass("com.gof.facadePattern.Boss1");Object obj1 = clazz1.newInstance();Method method1 = clazz1.getDeclaredMethod("sout", null);method1.invoke(obj1, null);System.out.println(clazz1.getClassLoader() );} }
為了好區分 我們把Boss1 的類 ,sout方法的輸出稍微調整下,以示區別。
應用中的Boss1 無需刪除
同時模擬第二個應用, 在D盤創建 artisan1/com/gof/facadePattern 目錄,將Boss類的復制類Boss1.class丟入該目錄
基于以上前置條件,得出如下結論
我們通過上面的示例模擬出了同一個JVM內, 分別使用不同的類加載器(new 出來的)去加載不同classpath下的類,而避免了走雙親委派,去模擬tomcat的類加載機制
通過結論可以得出在同一個JVM內,兩個相同包名和類名的類對象可以共存,是因為他們的類加載器不一樣。
所以看兩個類對象是否是同一個,除了看類的包名和類名是否都相同之外,還需要他們的類加載器是否相同
Tomcat加載機制小結
當tomcat啟動時,會創建幾種類加載器:
Bootstrap 引導類加載器 : 加載JVM啟動所需的類,以及標準擴展類(位于jre/lib/ext下)
System 系統類加載器 : 加載tomcat啟動的類,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
4. webapp 應用類加載器: 每個應用在部署后,都會創建一個唯一的類加載器。該類加載器會加載位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
總之 當應用需要到某個類時,則會按照下面的順序進行類加載:
1 使用bootstrap引導類加載器加載 (JVM 的東西 )
2 使用system系統類加載器加載 (tomcat的啟動類Bootstrap包)
3 使用WebAppClassLoader 加載 WEB-INF/classes (應用自定義的class)
4 使用WebAppClassLoader 加載在WEB-INF/lib (應用的依賴包)
5 使用common類加載器在CATALINA_HOME/lib中加載 (tomcat的依賴包,公共的,被各個應用共享的)
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的Tomcat - 模拟Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM - 实现自定义的ClassLoa
- 下一篇: MyBatis源码- SqlSessio