Tomcat源码解剖-自定义类加载器

前言

上篇博客中将tomcat源码导入IDEA后,阅读源码的时候发现tomcat在初始化的时候自定义了三个类加载器和其他的两个类加载器,这次就来说说这些自定义类加载器在tomcat中的用途。
自定义的类加载器

一、Tomcat自定义类加载器的定义

  • CommonLoader:加载Tomcat所需要的jar包和class文件,可以被Tomcat容器本身以及各个Webapp访问;
  • CatalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • SharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,对于Tomcat不可见;
  • WebAppClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
  • JasperLoader:它出现的目的就是为了被丢弃,加载路径仅仅是某个JSP文件所编译出来的那一个.class文件;

二、Tomcat为什么要自定义类加载器?

原因一:隔离同类库不同版本

一个tomcat是可以部署多个Web应用程序,那么多个应用程序可能依赖同一个类库的不同版本,应该隔离同一个类库的不同版本。
默认的类加载器加载一个类是不管你是什么版本的,只在乎你的全限定类名,通过这个全限定类名来加载某个类的;

假如:第一个Web应用程序使用了mybatis 3.2.2版本,第二个Web应用程序使用了mybatis 3.4.6版本。
根据默认类加载器加载规则,如果先加载了mybatis 3.2.2,第二个应用程序去加载mybatis 3.4.6的时候,显示该类已经被加载,那么第二个应用程序运行时就会报jar包冲突的错误。

tomcat如何解决:tomcat中为每一个应用都分配了一个独立的WebAppClassLoader类加载器,这个类加载器打破了JDK的双亲委派模型,用来隔离应用之间的jar包问题。

原因二:共享同类库同版本

多个应用程序如果依赖相同类库的相同版本,应该共享同类库的同版本,避免重复的类库被加载进JVM。

tomcat如何解决:tomcat通过自定义的ShareClassLoader共享类加载器实现让多个应用程序的相同类库的相同版本进行共享。

原因三:隔离Tomcat与应用程序的类库

tomcat也有自己依赖的类库,不能与应用程序的类库冲突,应该让容器的类库和程序的类库隔离开来。

tomcat如何解决:tomcat所依赖的jar包是通过commonLoader类加载器进行加载的,而应用程序的jar包、class文件是由WebAppCLassLoader来加载的。

原因四:热部署

修改jsp内容后无需重启项目将它重新加载到内存中。
默认的类加载器加载一个类首先会去验证这个类是否已经被加载,如果被加载过就不会再加载。

tomcat如何解决:每个jsp对应一个唯一的类加载器,当检测到jsp内容发生更改的时候,直接卸载唯一的类加载器,然后重新创建类加载器,并加载jsp文件。

三、Tomcat初始化时的三个类加载器

查看Bootstrap类的initClassLoaders()方法,该方法主要做的事情:初始化三个类加载器、三个类加载器的层次关系、加载tomcat的lib目录下的jar包(tomcat所依赖的jar包)。

private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if (commonLoader == null) {
            // no config file, default to this loader - we might be in a 'single' env.
            commonLoader = this.getClass().getClassLoader();
        }
        // catalinaLoader和sharedLoader的父类都是commonLoader类加载器
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

catalinaLoader和sharedLoader的父类都是commonLoader类加载器,那么如何证实呢?下面我们进入createClassLoader()核心方法查看。

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {
    // 从根目录下的conf/catalina.properties文件中找到 name.loader 对应的value,这个value就是每个加载器需要加载的所有jar包路径。
    // 其中只有commonLoader类加载器会加载tomcat所依赖的jar包,其他两个不会加载。
    String value = CatalinaProperties.getProperty(name + ".loader");
    // 如果value为空的类加载器直接返回commonLoader类加载器的实例,也就是commonLoader类加载器的子类。
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);
    List<Repository> repositories = new ArrayList<>();
    String[] repositoryPaths = getPaths(value);
    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(new Repository(repository, RepositoryType.DIR));
        }
    }
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

因为commonLoader类加载器调用createClassLoader()方法的时候传的第二个parent参数为null,则可以先断定它是自定义的顶级父类。
commonLoader的子类
根据上图可以看出:catalinaLoader和sharedLoader调用createClassLoader()方法的时候传的第二个parent参数为commonLoader,那么我可以断定它俩就是commonLoader的子类,下面结合源码证实。

根据createClassLoader()方法的这段代码 String value = CatalinaProperties.getProperty(name + “.loader”); 可以获取三个类加载器要加载的jar包路径,它最终会去到tomcat的conf/catalina.properties文件中获取。这里就不详细演示这个方法了,有兴趣的可以去调试运行查看。

然后进入到catalina.properties文件中查看三个类加载器所要加载类的路径,其中只有common.loader的值不为空,其他两个都是空值。那么可以得出结论:commonLoader类加载器会加载tomcat的lib目录下的所有jar包。
三个类加载器的加载路径
已知server.loader和shared.loader的值为空。
根据下图得出结论:catalinaLoader和sharedLoader得到的返回值value为null,所以直接返回commonLoader的实例,也就证实了catalinaLoader和sharedLoader就是commonLoader的子类。
返回commonLoader的实例
那么我们最后可以得出tomcat类加载器的层次关系:
tomcat类加载器的层次关系

四、WebAppClassLoader类加载器

WebAppClassLoader继承自WebappClassLoaderBase,而WebappClassLoaderBase继承自URLClassLoader类,并重写loadClass()方法,我们来看看WebAppClassLoader类加载器是如何来加载一个类。

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;
        
        checkStateForClassLoading(name);

        // 从本地缓存中检查该类是否已经被加载
        clazz = findLoadedClass0(name);
        // 如果被加载过则直接返回class实例
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        
        // 如果本地缓存没有,则检查上一级缓存
        clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        
        // 如果两个缓存都没有,则使用系统的类装载器进行装载,防止Web应用程序中的类覆盖J2EE的类
        String resourceName = binaryNameToPath(name, false);
        // 系统类加载器AppClassLoader
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            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;
        }

        if (tryLoadingFromJavaseLoader) {
            try {
                // 使用系统的类装载器进行装载,判断是不是系统类
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
        
        // 如果启用了SecurityManager访问此类的权限,则检查此类是否允许被载入,如果不允许,则抛出异常
        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);
                }
            }
        }
        
        boolean delegateLoad = delegate || filter(name, true);

        // 若打开了delegateLoad标志位,调用父装载器来加载。如果父装载器为null,使用系统类装载器装载
        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
            }
        }

        // 从本地仓库中载入相关类
        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
        }
        
        // 若当前仓库中没有需要的类,且delegateLoad标志位关闭,则使用父装载器。若父装载器为null,使用系统类装载器来装载
        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);
}

总结

第一次阅读源码,有始无终。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值