当前位置: 代码网 > 服务器>服务器>Tomcat > 源码剖析Tomcat类的加载原理

源码剖析Tomcat类的加载原理

2024年05月18日 Tomcat 我要评论
众所周知,java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个jvm中是不能加载相同类库的不同版本

众所周知,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<url> 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应用程序类加载器配置为,<loader delegate="true"/> 或者满足下列条件的类:
             *  当前类属于以下这些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类加载器打破了双亲委派机制,自定义类加载类的顺序:

  • 扩展类加载器(extclassloader)加载
  • web应用程序类加载器(webappclassloader)
  • 系统类加载器类(appclassloader)
  • 公共类加载器类(commonclassloader)

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

  • 扩展类加载器(extclassloader)加载
  • 系统类加载器类(appclassloader)
  • 公共类加载器类(commonclassloader)
  • 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<class<?>>() {
                    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文件的热加载功能。

以上就是源码剖析tomcat类的加载原理的详细内容,更多关于tomcat类加载的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com