

Tomcat - 都说Tomcat违背了双亲委派机制,到底对不对?
作为一个Web容器,Tomcat要解决什么问题 , Tomcat 如果使用默认的双亲委派类加载机制能不能行?

结合上面的4个问题,我们看下双亲委派能不能支持?
第一个问题,如果使用默认的类加载器机制,肯定是无法加载两个相同类库的不同版本的,如果使用双亲委派,让父加载器去加载 ,不管你是什么版本的,只要你的全限定类名一样,那肯定只有一份,APP 隔离 无法满足
第二个问题,默认的类加载器是能够实现的,很好理解嘛, 就是双亲委派的功能,保证唯一性。
第三个问题和第一个问题一样。
第四个问题, 要怎么实现jsp文件的热加载呢? jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?可以直接卸载掉这jsp文件的类加载器 .当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。 源码详见: org.apache.jasper.servlet.JasperLoader
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ...也不尽然,核心的Java的加载还是遵从双亲委派 。
Tomcat中 各个web应用自己的类加载器(WebAppClassLoader)会优先加载,打破了双亲委派机制。加载不到时再交给commonClassLoader走双亲委托 .
我们基于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启动时,会创建几种类加载器:
CATALINA_HOME/bin下

3. webapp 应用类加载器: 每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。
4. 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的依赖包,公共的,被各个应用共享的)