文章目錄
Pre
Tomcat - 都說Tomcat違背了雙親委派機制,到底對不對?
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 loaded
Class<?> 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;
}
}
@Override
protected 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,其中會把自定義類加載器的父加載器設置爲應用程序類加載器AppClassLoader
MyClassLoader 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文件。
- Common 通用類加載器:加載tomcat使用以及應用通用的一些類,位於
CATALINA_HOME/lib
下,比如servlet-api.jar
總之 當應用需要到某個類時,則會按照下面的順序進行類加載:
1 使用bootstrap引導類加載器加載 (JVM 的東西 )
2 使用system系統類加載器加載 (tomcat的啓動類Bootstrap包)
3 使用WebAppClassLoader 加載 WEB-INF/classes (應用自定義的class)
4 使用WebAppClassLoader 加載在WEB-INF/lib (應用的依賴包)
5 使用common類加載器在CATALINA_HOME/lib中加載 (tomcat的依賴包,公共的,被各個應用共享的)