Tomcat Catalina为什么不new出来原理解析

 更新时间:2022年08月20日 10:18:49   作者:请叫我黄同学  
这篇文章主要为大家介绍了Tomcat Catalina为什么不new出来原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

一、Catalina为什么不new出来?

掌握了Java的类加载器和双亲委派机制,现在我们就可以回答正题上来了,Tomcat的类加载器是怎么设计的?

1.Web容器的特性

Web容器有其自身的特殊性,所以在类加载器这块是不能完全使用JVM的类加载器的双亲委派机制的。在Web容器中我们应该要满足如下的特性:

隔离性

部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互隔离。设想一下,两个Web应用,一个使用了Spring3.0,另一个使用了新的的5.0,应用服务器使用一个类加载器,Web应用将会因为jar包覆盖而无法启动。

灵活性:

Web应用之间的类加载器相互独立,那么就能针对一个Web应用进行重新部署,此时Web应用的类加载器会被重建,而且不会影响其他的Web应用。如果采用一个类加载器,类之间的依赖是杂乱复杂的,无法完全移出某个应用的类。

性能:

性能也是一个比较重要的点。部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以互相共享。这个需求也很常见,例如,用户可能有10个使用Spring框架的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到Web容器的内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。

2.Tomcat类加载器结构

明白了Web容器的类加载器有多个,再来看tomcat的类加载器结构。

首先上张图,整体看下tomcat的类加载器:

可以看到在原先的java类加载器基础上,tomcat新增了几个类加载器,包括3个基础类加载器和每个Web应用的类加载器,其中3个基础类加载器可在conf/catalina.properties中配置,具体介绍下:

Common

以应用类加载器为父类,是tomcat顶层的公用类加载器,其路径由conf/catalina.properties中的common.loader指定,默认指向${catalina.base}/lib下的包。

Catalina

以Common类加载器为父类,是用于加载Tomcat应用服务器的类加载器,其路径由server.loader指定,默认为空,此时tomcat使用Common类加载器加载应用服务器。

Shared

以Common类加载器为父类,是所有Web应用的父类加载器,其路径由shared.loader指定,默认为空,此时tomcat使用Common类加载器作为Web应用的父加载器。

Web应用

以Shared类加载器为父类,加载/WEB-INF/classes目录下的未压缩的Class和资源文件以及/WEB-INF/lib目录下的jar包,该类加载器只对当前Web应用可见,对其他Web应用均不可见。

默认情况下,Common、Catalina、Shared类加载器是同一个,但可以配置3个不同的类加载器,使他们各司其职。

首先,Common类加载器复杂加载Tomcat应用服务器内部和Web应用均可见的类,如Servlet规范相关包和一些通用工具包。

其次,Catalina类加载器负责只有Tomcat应用服务器内部可见的类,这些类对Web应用不可见。比如,想实现自己的会话存储方案,而且该方案依赖了一些第三方包,当然是不希望这些包对Web应用可见,这时可以配置server.load,创建独立的Catalina类加载器。

再次,Shared类复杂加载Web应用共享类,这些类tomcat服务器不会依赖

3.Tomcat源码分析

3.1 CatalinClassLoader

首先来看看Tomcat的类加载器的继承结构

可以看到继承的结构和我们上面所写的类加载器的结构不同。

大家需要注意双亲委派机制并不是通过继承来实现的,而是相互之间组合而形成的。

所以AppClassLoader没有继承自 ExtClassLoader,WebappClassLoader也没有继承自AppClassLoader。

至于Common ClassLoader ,Shared ClassLoader,Catalina ClassLoader则是在启动时初始化的三个不同名字的URLClassLoader。

先来看看Bootstrap#init()方法。init方法会调用initClassLoaders,同样也会将Catalina ClassLoader设置到当前线程设置到当前线程,进入initClassLoaders来看看。

    private void initClassLoaders() {
        try {
            // 创建 commonLoader  catalinaLoader sharedLoader
            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();
            }
            // 默认情况下 server.loader 和 shared.loader 都为空则会返回 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);
        }
    }

我们可以看到在initClassLoaders()方法中完成了CommonClassLoader, CatalinaClassLoader,和SharedClassLoader的创建,而且进入到createClassLoader方法中。

可以看到这三个基础类加载器所加载的资源刚好对应conf/catalina.properties中的common.loader,server.loader,shared.loader

3.2 层次结构

Common/Catalina/Shared ClassLoader的创建好了之后就会维护相互之间的组合关系

其实也就是设置了父加载器

3.3 具体的加载过程

源码比较长,直接进入到 WebappClassLoaderBase中的 LoadClass方法

@Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled()) {
                log.debug("loadClass(" + name + ", " + resolve + ")");
            }
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);
            // (0) Check our previously loaded local class cache
            // 检查WebappClassLoader中是否加载过此类
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
            // (0.1) Check our previously loaded class cache
            // 如果第一步没有找到,则继续检查JVM虚拟机中是否加载过该类
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
            // (0.2) Try loading the class with the bootstrap class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            // 如果前两步都没有找到,则使用系统类加载该类(也就是当前JVM的ClassPath)。
            // 为了防止覆盖基础类实现,这里会判断class是不是JVMSE中的基础类库中类。
            String resourceName = binaryNameToPath(name, false);
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                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) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (0.5) Permission to access this class when using a 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);
                    }
                }
            }
            // 检查是否 设置了delegate属性,设置为true,那么就会完全按照JVM的"双亲委托"机制流程加载类。
            boolean delegateLoad = delegate || filter(name, true);
            // (1) Delegate to our parent if requested
            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
                }
            }
            // (2) Search local repositories
            // 若是没有委托,则默认会首次使用WebappClassLoader来加载类。通过自定义findClass定义处理类加载规则。
            // findClass()会去Web-INF/classes 目录下查找类。
            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
            }
            // (3) Delegate to parent unconditionally
            // 若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下还是查找不到class,
            // 那么无条件强制委托给System、Common类加载器去查找该类。
            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);
    }

Web应用类加载器默认的加载顺序是:

  • (1).先从缓存中加载;
  • (2).如果没有,则从JVM的Bootstrap类加载器加载;
  • (3).如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序);
  • (4).如果没有,则从父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序是AppClassLoader、Common、Shared。

tomcat提供了delegate属性用于控制是否启用java委派模式,默认false(不启用),当设置为true时,tomcat将使用java的默认委派模式,这时加载顺序如下:

  • (1).先从缓存中加载;
  • (2).如果没有,则从JVM的Bootstrap类加载器加载;
  • (3).如果没有,则从父类加载器加载,加载顺序是AppClassLoader、Common、Shared。
  • (4).如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)

以上就是Tomcat Catalina为什么不new出来原理解析的详细内容,更多关于Tomcat Catalina原理的资料请关注脚本之家其它相关文章!

相关文章

  • CentOS设置IP连接网络实现过程图解

    CentOS设置IP连接网络实现过程图解

    这篇文章主要介绍了CentOS设置IP连接网络实现过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 使用JMX监控Tomcat示例代码

    使用JMX监控Tomcat示例代码

    这篇文章主要介绍了使用JMX监控Tomcat示例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Tomcat日志文件详解及catalina.out日志清理方法小结

    Tomcat日志文件详解及catalina.out日志清理方法小结

    Tomcat作为常用Java应用服务器,会生成多种日志文件辅助排查问题与优化系统,其中catalina.out文件记录重要输出信息,但长期累积会占用大量磁盘空间,影响性能,本文介绍Tomcat日志文件种类及其作用,并重点讨论如何安全有效地清理catalina.out文件
    2024-10-10
  • eclipse配置Tomcat找不到server选项的解决办法

    eclipse配置Tomcat找不到server选项的解决办法

    这篇文章通过图文并茂的形式给大家介绍eclipse配置Tomcat找不到server选项的解决办法,感兴趣的朋友跟随脚本之家小编一起学习吧
    2018-05-05
  • Tomcat管理平台_动力节点Java学院整理

    Tomcat管理平台_动力节点Java学院整理

    这篇文章主要为大家详细介绍了Tomcat管理平台的相关资料,讲解Tomcat服务器的管理平台具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 一文教你怎么选择Tomcat对应的JDK版本

    一文教你怎么选择Tomcat对应的JDK版本

    这篇文章主要给大家截介绍了怎么选择Tomcat对应的JDK版本,如果不知道Tomcat的哪个版本应该对应哪个版本的JDK,可以参考借鉴本文,对大家的学习有一定的帮助
    2023-10-10
  • 解析Tomcat的启动脚本--catalina.bat

    解析Tomcat的启动脚本--catalina.bat

    本文主要对Tomcat的三个最重要的启动脚本之一--catalina.bat脚本做了详细分析,具有很好的参考价值,需要的朋友可以看下
    2016-12-12
  • Web应用中设置Context Path案例详解

    Web应用中设置Context Path案例详解

    这篇文章主要介绍了Web应用中设置Context Path案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • tomcat服务安装步骤及详细配置实战教程

    tomcat服务安装步骤及详细配置实战教程

    Tomcat是由Apache开发的一个开源Java WEB应用服务器,下面这篇文章主要给大家介绍了关于tomcat服务安装步骤及详细配置实战教程,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • Tomcat注册成服务的几个注意点小结

    Tomcat注册成服务的几个注意点小结

    这篇文章主要介绍了Tomcat注册成服务的几个注意点,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08

最新评论