简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。
- 启动类加载器:由C++实现,负责加载JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
- 扩展类加载器:负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
- 应用程序类加载器:负责加载用户路径(classpath)上的类库。
- 自定义类加载器:通过继承java.lang.ClassLoader实现自定义的类加载器。
什么是双亲委派机制?
为了防止内存中存在多份同样的字节码,使用了双亲委派机制(它不会自己去尝试加载类,而是把请求委托给父加载器去完成,依次向上),双亲委派模型可以确保安全性,可以保证所有的java类库都是由启动类加载器加载。
什么是打破了双亲委派机制?
因为加载class核心的方法在LoaderClass类的loadClass方法上(双亲委派机制的核心实现),那只要我自定义个ClassLoader,重写loadClass方法(不依照往上开始寻找类加载器),那就算是打破双亲委派机制了。
Tomcat是怎么打破双亲委派机制的呢?
虽说现在都流行使用 Springboot 开发 web 应用,Tomcat 内嵌在 SpringBoot 中。而在此之前,我们会使用最原生的方式,servlet + Tomcat 的方式开发和部署 web 程序。
一个 Tomcat 可能会部署多个这样的 web 应用,不同的 web 应用可能会依赖同一个第三方库的不同版本,为了保证每个 web 应用的类库都是独立的,需要实现类隔离。而Tomcat 的自定义类加载器 WebAppClassLoader 解决了这个问题。
每一个 web 应用都会对应一个 WebAppClassLoader 实例,不同的类加载器实例加载的类是不同的,Web应用之间通各自的类加载器相互隔离。
当然 Tomcat自定义类加载器不只解决上面的问题,WebAppClassLoader 打破了双亲委派机制,即它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载Web应用定义的类。
Tomcat 的类加载器是怎么打破双亲委派的机制的。我们先看代码:
findClass 方法
@Override
public Class<?> findClass(string name) throws ClassNotFoundException {
// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
// 先在自己的 Web 应用目录下查找 class
clazz = findClassInternal(name);
// 找不到 在交由父类来处理
if ((clazz == null) && hasExternalRepositories) {
clazz = super.findClass(name);
}
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
对于 Tomcat 的类加载的 findClass 方法:
- 首先在 web 目录下查找。(重要)
- 找不到再交由父类的 findClass 来处理。
- 都找不到,那就抛出 ClassNotFoundException。
loadClass方法
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
//1. 先在本地cache查找该类是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
//2. 从系统类加载器的cache中查找是否加载过
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
// 3. 尝试用ExtClassLoader类加载器类加载
ClassLoader JavaSELoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 4. 尝试在本地目录搜索class并加载
try {
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
//6. 上述过程都加载失败,抛出异常
throw new ClassNotFoundException(name);
}
加载的步骤:
- 先在本地cache查找该类是否已经加载过,看看 Tomcat 有没有加载过这个类。
- 如果Tomcat 没有加载过这个类,则从系统类加载器的cache中查找是否加载过。
- 如果没有加载过这个类,尝试用ExtClassLoader类加载器类加载,重点来了,这里并没有首先使用 AppClassLoader 来加载类。这个Tomcat 的 WebAPPClassLoader 违背了双亲委派机制,直接使用了 ExtClassLoader来加载类。这里注意 ExtClassLoader 双亲委派依然有效,ExtClassLoader 就会使用 Bootstrap ClassLoader 来对类进行加载,保证了 JRE 里面的核心类不会被重复加载。 比如在 Web 中加载一个 Object 类。WebAppClassLoader → ExtClassLoader → Bootstrap ClassLoader,这个加载链,就保证了 Object 不会被重复加载。
- 如果 BoostrapClassLoader,没有加载成功,就会调用自己的 findClass 方法由自己来对类进行加载,findClass 加载类的地址是自己本 web 应用下的 class。
- 加载依然失败,才使用 AppClassLoader 继续加载。
- 都没有加载成功的话,抛出异常。
总结一下以上步骤,WebAppClassLoader 加载类的时候,故意打破了JVM 双亲委派机制,绕开了 AppClassLoader,直接先使用 ExtClassLoader 来加载类。
- 保证了基础类不会被同时加载。
- 由保证了在同一个 Tomcat 下不同 web 之间的 class 是相互隔离的。