• 从源码级深入剖析Tomcat类加载原理


    众所周知,Java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个JVM中是不能加载相同类库的不同版本的类。
    然而与许多服务器应用程序一样,Tomcat 允许容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库,这就要求Tomcat必须打破这种双亲委派机制,通过实现自定义的类加载器(即实现了java.lang.ClassLoader)进行类的加载。下面,就让我们来看看Tomcat类加载原理是怎样的。

    Tomcat中有两个最重要的类加载器,第一个便是负责Web应用程序类加载的WebappClassLoader,另一个便是JSP Servlet类加载器`JasperLoader。

    Web应用程序类加载器(WebappClassLoader)

    上代码:

    public class WebappClassLoader extends WebappClassLoaderBase {
        public WebappClassLoader() {
            super();
        }
        public WebappClassLoader(ClassLoader parent) {
            super(parent);
        } 
       ...
    }
    

    我们来看看WebappClassLoader继承的WebappClassLoaderBase中实现的类加载方法loadClass

    public abstract class WebappClassLoaderBase extends URLClassLoader
            implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
    	...	省略不需要关注的代码
        protected WebappClassLoaderBase() {
    
            super(new URL[0]);
    		// 获取当前WebappClassLoader的父加载器系统类加载器
            ClassLoader p = getParent();
            if (p == null) {
                p = getSystemClassLoader();
            }
            this.parent = p;
    		// javaseClassLoader变量经过以下代码的执行,
    		// 得到的是扩展类加载器(ExtClassLoader)
            ClassLoader j = String.class.getClassLoader();
            if (j == null) {
                j = getSystemClassLoader();
                while (j.getParent() != null) {
                    j = j.getParent();
                }
            }
            this.javaseClassLoader = j;
    
            securityManager = System.getSecurityManager();
            if (securityManager != null) {
                refreshPolicy();
            }
        }
    
        ...省略不需要关注的代码
        @Override
        public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
            synchronized (getClassLoadingLock(name)) {
                if (log.isDebugEnabled()) {
                    log.debug("loadClass(" + name + ", " + resolve + ")");
                }
                Class clazz = null;
    
                // Web应用程序停止状态时,不允许加载新的类
                checkStateForClassLoading(name);
    
                // 如果之前加载过该类,就可以从Web应用程序类加载器本地类缓存中查找,
    			// 如果找到说明WebappClassLoader之前已经加载过这个类
                clazz = findLoadedClass0(name);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Returning class from cache");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
    
                // Web应用程序本地类缓存中没有,可以从系统类加载器缓存中查找,
    			// 如果找到说明AppClassLoader之前已经加载过这个类
                clazz = findLoadedClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Returning class from cache");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
    
    			// 将类似java.lang.String这样的类名这样转换成java/lang/String
    			// 这样的资源文件名
                String resourceName = binaryNameToPath(name, false);
    			// 获取引导类加载器(BootstrapClassLoader)
                ClassLoader javaseLoader = getJavaseClassLoader();
                boolean tryLoadingFromJavaseLoader;
                try {
    		    // 引导类加载器根据转换后的类名获取资源url,如果url不为空,就说明找到要加载的类
                    URL url;
                    if (securityManager != null) {
                        PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName);
                        url = AccessController.doPrivileged(dp);
                    } else {
                        url = javaseLoader.getResource(resourceName);
                    }
                    tryLoadingFromJavaseLoader = (url != null);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    tryLoadingFromJavaseLoader = true;
                }
    
               // 首先,从扩展类加载器(ExtClassLoader)加载,防止Java核心API库被Web应用程序类随意篡改
               if (tryLoadingFromJavaseLoader) {
                    try {
                        clazz = javaseLoader.loadClass(name);
                        if (clazz != null) {
                            if (resolve) {
                                resolveClass(clazz);
                            }
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
    
                // 当使用安全管理器时,允许访问这个类
                if (securityManager != null) {
                    int i = name.lastIndexOf('.');
                    if (i >= 0) {
                        try {
                            securityManager.checkPackageAccess(name.substring(0,i));
                        } catch (SecurityException se) {
                            String error = sm.getString("webappClassLoader.restrictedPackage", name);
                            log.info(error, se);
                            throw new ClassNotFoundException(error, se);
                        }
                    }
                }
                /* 
                 *  如果Web应用程序类加载器配置为, 或者满足下列条件的类:
                 *  当前类属于以下这些jar包中:
                 *  annotations-api.jar — Common Annotations 1.2 类。
                 *  catalina.jar — Tomcat 的 Catalina servlet 容器部分的实现。
                 *  catalina-ant.jar — 可选。用于使用 Manager Web 应用程序的 Tomcat Catalina Ant 任务。
                 *  catalina-ha.jar — 可选。提供基于 Tribes 构建的会话集群功能的高可用性包。
                 *  catalina-storeconfig.jar — 可选。从当前状态生成 XML 配置文件。
                 *  catalina-tribes.jar — 可选。高可用性包使用的组通信包。
                 *  ecj-*.jar — 可选。Eclipse JDT Java 编译器用于将 JSP 编译为 Servlet。
                 *  el-api.jar — 可选。EL 3.0 API。
                 *  jasper.jar — 可选。Tomcat Jasper JSP 编译器和运行时。
                 *  jasper-el.jar — 可选。Tomcat EL 实现。
                 *  jaspic-api.jar — JASPIC 1.1 API。
                 *  jsp-api.jar — 可选。JSP 2.3 API。
                 *  servlet-api.jar — Java Servlet 3.1 API。
                 *  tomcat-api.jar — Tomcat 定义的几个接口。
                 *  tomcat-coyote.jar — Tomcat 连接器和实用程序类。
                 *  tomcat-dbcp.jar — 可选。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的
                 *      包重命名副本的数据库连接池实现。
                 *  tomcat-i18n-**.jar — 包含其他语言资源包的可选 JAR。由于默认包也包含在每个单独的JAR
                 *      中,如果不需要消息国际化,可以安全地删除它们。
                 *  tomcat-jdbc.jar — 可选。另一种数据库连接池实现,称为 Tomcat JDBC 池。有关详细信息,请参阅 文档。
                 *  tomcat-jni.jar — 提供与 Tomcat Native 库的集成。
                 *  tomcat-util.jar — Apache Tomcat 的各种组件使用的通用类。
                 *  tomcat-util-scan.jar — 提供 Tomcat 使用的类扫描功能。
                 *  tomcat-websocket.jar — 可选。Java WebSocket 1.1 实现
                 *  websocket-api.jar — 可选。Java WebSocket 1.1 API
                 *  
                 *  此处的filter方法,实际上tomcat官方将filter类加载过滤条件,看作是一种类加载器,
    	     *        将其取名为CommonClassLoader
                 */
                boolean delegateLoad = delegate || filter(name, true);
    
                // 如果ExtClassLoader没有获取到,说明是非JRE核心类,那么就从系统类加载器(也称AppClassLoader
    			// 应用程序类加载器)加载
                if (delegateLoad) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Delegating to parent classloader1 " + parent);
                    }
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled()) {
                                log.debug("  Loading class from parent");
                            }
                            if (resolve) {
                                resolveClass(clazz);
                            }
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
    
                // 从Web应用程序的类加载器(也就是WebappClassLoader)中加载类。Web应用程序的类加载器是
    			// 一个特殊的类加载器,它负责从Web应用程序的本地库中加载类
                if (log.isDebugEnabled()) {
                    log.debug("  Searching local repositories");
                }
                try {
                    clazz = findClass(name);
                    if (clazz != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from local repository");
                        }
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
    
                // 经过上面几个步骤还未加载到类,则采用系统类加载器(也称应用程序类加载器)进行加载
                if (!delegateLoad) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Delegating to parent classloader at end: " + parent);
                    }
                    try {
                        clazz = Class.forName(name, false, parent);
                        if (clazz != null) {
                            if (log.isDebugEnabled()) {
                                log.debug("  Loading class from parent");
                            }
                            if (resolve) {
                                resolveClass(clazz);
                            }
                            return clazz;
                        }
                    } catch (ClassNotFoundException e) {
                        // Ignore
                    }
                }
            }
            // 最终,还未加载到类,报类未找到的异常
            throw new ClassNotFoundException(name);
        }
    	...省略不需要关注的代码
    }
    

    综上所述,我们得出WebappClassLoader类加载器打破了双亲委派机制,自定义类加载类的顺序:

    1. 扩展类加载器(ExtClassLoader)加载
    2. Web应用程序类加载器(WebappClassLoader)
    3. 系统类加载器类(AppClassLoader)
    4. 公共类加载器类(CommonClassLoader)

    如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:

    1. 扩展类加载器(ExtClassLoader)加载
    2. 系统类加载器类(AppClassLoader)
    3. 公共类加载器类(CommonClassLoader)
    4. Web应用程序类加载器(WebappClassLoader)

    JSP类加载器(JasperLoader)

    上代码:

    public class JasperLoader extends URLClassLoader {
    
        private final PermissionCollection permissionCollection;
        private final SecurityManager securityManager;
    
        // JSP类加载器的父加载器是Web应用程序类加载器(WebappClassLoader)
        public JasperLoader(URL[] urls, ClassLoader parent,
                            PermissionCollection permissionCollection) {
            super(urls, parent);
            this.permissionCollection = permissionCollection;
            this.securityManager = System.getSecurityManager();
        }
    
        @Override
        public Class loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    
        @Override
        public synchronized Class loadClass(final String name, boolean resolve)
            throws ClassNotFoundException {
    
            Class clazz = null;
    
            // 从JVM的类缓存中查找
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
    
            // 当使用SecurityManager安全管理器时,允许访问访类
            if (securityManager != null) {
                int dot = name.lastIndexOf('.');
                if (dot >= 0) {
                    try {
                        // Do not call the security manager since by default, we grant that package.
                        if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
                            securityManager.checkPackageAccess(name.substring(0,dot));
                        }
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        se.printStackTrace();
                        throw new ClassNotFoundException(error);
                    }
                }
            }
           // 如果类名不是以org.apache.jsp包名开头的,则采用WebappClassLoader加载
            if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
                // Class is not in org.apache.jsp, therefore, have our
                // parent load it
                clazz = getParent().loadClass(name);
                if( resolve ) {
                    resolveClass(clazz);
                }
                return clazz;
            }
    	// 如果是org.apache.jsp包名开头JSP类,就调用父类URLClassLoader的findClass方法
    	// 动态加载类文件,解析成Class类,返回给调用方
            return findClass(name);
        }
    }
    

    下面是URLClassLoader的findClass方法,具体实现:

    protected Class findClass(final String name)
            throws ClassNotFoundException
        {
            final Class result;
            try {
                result = AccessController.doPrivileged(
                    new PrivilegedExceptionAction>() {
                        public Class run() throws ClassNotFoundException {
                            String path = name.replace('.', '/').concat(".class");
                            Resource res = ucp.getResource(path, false);
                            if (res != null) {
                                try {
     				// 解析类的字节码文件生成Class类对象
                                    return defineClass(name, res);
                                } catch (IOException e) {
                                    throw new ClassNotFoundException(name, e);
                                }
                            } else {
                                return null;
                            }
                        }
                    }, acc);
            } catch (java.security.PrivilegedActionException pae) {
                throw (ClassNotFoundException) pae.getException();
            }
            if (result == null) {
                throw new ClassNotFoundException(name);
            }
            return result;
        }
    

    从源码中我们可以看到,JSP类加载原理是,先从JVM类缓存中(也就是Bootstrap类加载器加载的类)加载,如果不是核心类库的类,就从Web应用程序类加载器WebappClassLoader中加载,如果还未找到,就说明是jsp类,则通过动态解析jsp类文件获得要加载的类。

    经过上面两个Tomcat核心类加载器的剖析,我们也就知道了Tomcat类的加载原理了。
    下面我们来总结一下:Tomcat会为每个Web应用程序创建一个WebappClassLoader类加载器进行类的加载,不同的类加载器实例加载的类是会被认为是不同的类,即使它们的类名相同,这样的话就可以实现在同一个JVM下,允许Tomcat容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库。
    针对JSP类,会由专门的JSP类加载器(JasperLoader)进行加载,该加载器会针对JSP类在每次加载时都会解析类文件,Tomcat容器会启动一个后台线程,定时检测JSP类文件的变化,及时更新类文件,这样就实现JSP文件的热加载功能。

    如果您觉得博文不错,请用微信扫描右上方二维码,关注我的微信公众号“编程老司机”,获取最新推送文章。

  • 相关阅读:
    PostgreSQL文本搜索(六)——词典
    嵌入式软件开发中必备软件工具
    第16集丨阳明心学&量子力学
    参加微软学生开发者峰会,了解Azure和GitHub……
    海思3559万能平台:VGS的画线处理
    【小程序源码】笑话段子手
    前端面试题集锦(4)
    SpringBoot开启异步多线程
    AAAAAAAAA
    IDEA2021中使用Git(2.38.1)和Gitee
  • 原文地址:https://www.cnblogs.com/springmorning/p/17488257.html