org.apache.catalina.loader.WebappClassLoaderBase#findClass;为了方便理解和阅读,我去掉了一些细节:
public Class<?> findClass(String name) throws ClassNotFoundException { ... Class<?> clazz = null; try { //1. 先在 Web 应用目录下查找类 clazz = findClassInternal(name); } catch (RuntimeException e) { throw e; } if (clazz == null) { try { //2. 如果在本地目录没有找到,交给父加载器去查找 clazz = super.findClass(name); } catch (RuntimeException e) { throw e; } //3. 如果父类也没找到,抛出 ClassNotFoundException if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }先在 Web 应用本地目录下查找要加载的类。
如果没有找到,交给父加载器去查找,它的父加载器就是上面提到的系统类加载器 AppClassLoader。
如何父加载器也没找到这个类,抛出 ClassNotFound异常。
loadClass 方法再来看 Tomcat 类加载器的 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 类加载器没有加载过这个类,再看看系统类加载器是否加载过。
如果都没有,就让ExtClassLoader去加载,这一步比较关键,目的 防止 Web 应用自己的类覆盖 JRE 的核心类。因为 Tomcat 需要打破双亲委托机制,假如 Web 应用里自定义了一个叫 Object 的类,如果先加载这个 Object 类,就会覆盖 JRE 里面的那个 Object 类,这就是为什么 Tomcat 的类加载器会优先尝试用 ExtClassLoader去加载,因为 ExtClassLoader会委托给 BootstrapClassLoader去加载,BootstrapClassLoader发现自己已经加载了 Object 类,直接返回给 Tomcat 的类加载器,这样 Tomcat 的类加载器就不会去加载 Web 应用下的 Object 类了,也就避免了覆盖 JRE 核心类的问题。
如果 ExtClassLoader加载器加载失败,也就是说 JRE核心类中没有这类,那么就在本地 Web 应用目录下查找并加载。
如果本地目录下没有这个类,说明不是 Web 应用自己定义的类,那么由系统类加载器去加载。这里请你注意,Web 应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。
如果上述加载过程全部失败,抛出 ClassNotFound异常。
Tomcat 类加载器层次Tomcat 作为 Servlet容器,它负责加载我们的 Servlet类,此外它还负责加载 Servlet所依赖的 JAR 包。并且 Tomcat本身也是也是一个 Java 程序,因此它需要加载自己的类和依赖的 JAR 包。首先让我们思考这一下这几个问题:
假如我们在 Tomcat 中运行了两个 Web 应用程序,两个 Web 应用中有同名的 Servlet,但是功能不同,Tomcat 需要同时加载和管理这两个同名的 Servlet类,保证它们不会冲突,因此 Web 应用之间的类需要隔离。
假如两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 Spring的 JAR 包被加载到内存后,Tomcat要保证这两个 Web 应用能够共享,也就是说 Spring的 JAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,JVM的内存会膨胀。
跟 JVM 一样,我们需要隔离 Tomcat 本身的类和 Web 应用的类。
1. WebAppClassLoader