Tomcat是如何打破"双亲委派"机制的?

简介: 上文我们详细了解了类加载以及什么是双亲委派机制,相信很多童鞋都了解Tomcat打破了双亲委派机制,本文将对Tomcat为什么要打破双亲委派机制,以及Tomcat是如何打破双亲委派机制的,进行完整性的复盘与解析。

前言

image.png
上文我们详细了解了类加载以及什么是双亲委派机制,相信很多童鞋都了解Tomcat打破了双亲委派机制,本文将对Tomcat为什么要打破双亲委派机制,以及Tomcat是如何打破双亲委派机制的,进行完整性的复盘与解析,且听我慢慢道来。

Tomcat为什么要打破"双亲委派"?

不知道各位看官有没有想过,Tomcat为什么要打破双亲委派机制呢?

image.png
Tomcat打破双亲委派机制的目的其实很简单,我们知道web容器可能是需要部署多个应用程序的,这在早期的部署架构中也经常见到,如上图。但是假设不同的应用程序可能会同时依赖第三方类库的不同版本。我们通过上文在了解类加载机制的时候知道它是要确保唯一性的,但是总不能要求同一个类库在web容器中只有一份吧?所以Tomcat就需要保证每个应用程序的类库都是相互隔离并独立的,这也是它为什么打破双亲委派机制的主要目的。

Tomcat类加载概述

Tomcat的ClassLoader层级如下所示image.png

  • CommonClassLoader(通用类加载器):主要负责加载${catalina.base}/lib定义的目录和jar以及${catalina.home}/lib定义的目录和jar,可以被Tomcat和所有的Web应用程序共同使用。
  • ※WebAppClassLoader(web应用的类加载器):tomcat加载应用的核心类加载器,每个Web应用程序都有一个WebAppClassLoader,类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web程序都不可见。

Tomcat类加载器初始化过程

我们可以在org.apache.catalina.startup.Bootstrap看到如下代码:

private void initClassLoaders() {  
    try {
        
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }  
        //初始化其它两个类加载器  
            catalinaLoader = createClassLoader("server", commonLoader);  
            sharedLoader = createClassLoader("shared", commonLoader);  
        } catch (Throwable t) {  
            log.error("Class loader creation threw exception", t);  
            System.exit(1);  
        }  
    }
    private void initClassLoaders() {
        try {
            // 创建CommonClassLoader
            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();
            }
            // 根据配置创建SharedClassLoader、CatalinaClassLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }


    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        // 读取catalina.properties文件中的配置
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 没有对应的配置,不会创建此类加载器,而是返回传入的父类加载器,也就是CommonClassLoader
        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);
    }

Tomcat是如何打破双亲委派机制的呢?

从上文中,我们不难看出,真正实现web应用程序之间的类加载器相互隔离独立的是WebAppClassLoader类加载器。它为什么可以隔离每个web应用程序呢?原因就是它打破了"双亲委派"的机制,如果收到类加载的请求,它会先尝试自己去加载,如果找不到在交给父加载器去加载,这么做的目的就是为了优先加载Web应用程序自己定义的类来实现web应用程序相互隔离独立的。

WebappClassLoader底层原理

我们知道ClassLoader默认的loadClass方法是以双亲委派的模型进行加载类的,那么想要加载自定义资源打破"双亲委派"的机制,那么Tomcat就要必定要重写findClass与loadClass方法,如下所示:

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {

        if (log.isDebugEnabled())
            log.debug("    findClass(" + name + ")");

        checkStateForClassLoading(name);

        // (1) Permission to define this class when using a SecurityManager
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    if (log.isTraceEnabled())
                        log.trace("      securityManager.checkPackageDefinition");
                    securityManager.checkPackageDefinition(name.substring(0,i));
                } catch (Exception se) {
                    if (log.isTraceEnabled())
                        log.trace("      -->Exception-->ClassNotFoundException", se);
                    throw new ClassNotFoundException(name, se);
                }
            }
        }

        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            if (log.isTraceEnabled())
                log.trace("      findClassInternal(" + name + ")");
            try {
                if (securityManager != null) {
                    PrivilegedAction<Class<?>> dp =
                        new PrivilegedFindClassByName(name);
                    clazz = AccessController.doPrivileged(dp);
                } else {
                     // 1、先在应用本地目录下查找类 
                    clazz = findClassInternal(name);
                }
            } catch(AccessControlException ace) {
                log.warn("WebappClassLoader.findClassInternal(" + name
                        + ") security exception: " + ace.getMessage(), ace);
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
                if (log.isTraceEnabled())
                    log.trace("      -->RuntimeException Rethrown", e);
                throw e;
            }
            if ((clazz == null) && hasExternalRepositories) {
                try {
                     // 2、如果在本地目录没有找到,委派父加载器去查找
                    clazz = super.findClass(name);
                } catch(AccessControlException ace) {
                    log.warn("WebappClassLoader.findClassInternal(" + name
                            + ") security exception: " + ace.getMessage(), ace);
                    throw new ClassNotFoundException(name, ace);
                } catch (RuntimeException e) {
                    if (log.isTraceEnabled())
                        log.trace("      -->RuntimeException Rethrown", e);
                    throw e;
                }
            }
            // 3、如果父加载器也没找到,抛出异常
            if (clazz == null) {
                if (log.isDebugEnabled())
                    log.debug("    --> Returning ClassNotFoundException");
                throw new ClassNotFoundException(name);
            }
        } catch (ClassNotFoundException e) {
            if (log.isTraceEnabled())
                log.trace("    --> Passing on ClassNotFoundException");
            throw e;
        }

        // Return the class we have located
        if (log.isTraceEnabled())
            log.debug("      Returning class " + clazz);

        if (log.isTraceEnabled()) {
            ClassLoader cl;
            if (Globals.IS_SECURITY_ENABLED){
                cl = AccessController.doPrivileged(
                    new PrivilegedGetClassLoader(clazz));
            } else {
                cl = clazz.getClassLoader();
            }
            log.debug("      Loaded by " + cl.toString());
        }
        return (clazz);

    }
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        // 1、从本地缓存中查找是否加载过此类
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 2、从AppClassLoader中查找是否加载过此类
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        String resourceName = binaryNameToPath(name, false);
        // 3、尝试用ExtClassLoader 类加载器加载类,防止应用覆盖JRE的核心类
        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) {
            tryLoadingFromJavaseLoader = true;
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 4、判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
        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
            }
        }

        // 5、默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
        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
        }

        // 6、如果在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
        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);
}

小结

  • 先在本地缓存中查找该类是否已经加载过,如果加载过就返回缓存中的。
  • 如果没有加载过,委托给AppClassLoader是否加载过,如果加载过就返回。
  • 如果AppClassLoader也没加载过,委托给ExtClassLoader去加载,这么做的目的就是:

    • 防止应用自己的类库覆盖了核心类库,因为WebAppClassLoader需要打破双亲委托机制,假如应用里自定义了一个叫java.lang.String的类,如果先加载这个类,就会覆盖核心类库的java.lang.String,所以说它会优先尝试用ExtClassLoader去加载,因为ExtClassLoader加载不到同样也会委托给BootstrapClassLoader去加载,也就避免了覆盖了核心类库的问题。
  • 如果ExtClassLoader也没有查找到,说明核心类库中没有这个类,那么就在本地应用目录下查找此类并加载。
  • 如果本地应用目录下还有没有这个类,那么肯定不是应用自己定义的类,那么就由AppClassLoader去加载。

    • 这里是通过Class.forName()调用AppClassLoader类加载器的,因为Class.forName()的默认加载器就是AppClassLoader。
  • 如果上述都没有找到,那么只能抛出ClassNotFoundException了。
目录
相关文章
|
7月前
|
安全 Java 应用服务中间件
打破Tomcat中的双亲委派机制:探讨与实践
打破Tomcat中的双亲委派机制:探讨与实践
|
安全 Java 应用服务中间件
【JavaWeb】Tomcat底层机制和Servlet运行原理
网络通信:Tomcat使用Java的Socket API来监听特定的端口(通常是8080),接收来自客户端的HTTP请求。 线程池:Tomcat使用线程池来处理并发的请求。当有新的请求到达时,Tomcat会从线程池中获取一个空闲线程来处理该请求,这样可以提高处理效率。 生命周期管理:Tomcat负责管理Servlet和其他Web组件的生命周期,包括初始化、请求处理和销毁等阶段。(init(), run())
|
2月前
|
网络协议 Java 应用服务中间件
深入浅出Tomcat网络通信的高并发处理机制
【10月更文挑战第3天】本文详细解析了Tomcat在处理高并发网络请求时的机制,重点关注了其三种不同的IO模型:NioEndPoint、Nio2EndPoint 和 AprEndPoint。NioEndPoint 采用多路复用模型,通过 Acceptor 接收连接、Poller 监听事件及 Executor 处理请求;Nio2EndPoint 则使用 AIO 异步模型,通过回调函数处理连接和数据就绪事件;AprEndPoint 通过 JNI 调用本地库实现高性能,但已在 Tomcat 10 中弃用
深入浅出Tomcat网络通信的高并发处理机制
|
3月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
5月前
|
Java 应用服务中间件 API
开发与运维机制问题之Tomcat要打破双亲委派机制如何解决
开发与运维机制问题之Tomcat要打破双亲委派机制如何解决
42 0
|
7月前
|
Java 应用服务中间件 容器
JavaWeb手写Tomcat底层机制
综上所述,Tomcat作为JavaWeb应用的Servlet容器,在接收请求、解析请求、查找Servlet、创建请求和响应对象、请求分发、生成响应、连接管理等方面起着关键作用。其底层机制通过Socket通信、Servlet生命周期管理、线程池、Session管理等技术实现了整个JavaWeb应用的运行。
44 0
|
7月前
|
缓存 负载均衡 应用服务中间件
【分布式技术专题】「分析Web服务器架构」Tomcat服务器的运行架构和LVS负载均衡的运行机制(修订版)
在本章内容中,我们将深入探讨 Tomcat 服务器的运行架构、LVS 负载均衡的运行机制以及 Cache 缓存机制,并提供相应的解决方案和指导。通过理解这些关键概念和机制,您将能够优化您的系统架构,提高性能和可扩展性。
314 4
【分布式技术专题】「分析Web服务器架构」Tomcat服务器的运行架构和LVS负载均衡的运行机制(修订版)
|
Java 应用服务中间件 Maven
JavaWeb 手写Tomcat底层机制
JavaWeb——手写Tomcat底层 BIO线程模型 + 反射机制。
58 0
|
存储 Java 应用服务中间件
|
Java 应用服务中间件 API
Tomcat 的运行机制
转载地址:  http://wiki.jikexueyuan.com/project/java-web/00-08.html 先不去关技术细节,对一个servlet容器,我觉得它首先要做以下事情:  1:实现Servlet api规范。这是最基础的一个实现,servlet api大部分都是接口规范。如request、response、session、cookie。为了我们应用端
1388 0
下一篇
无影云桌面