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 应用服务中间件 Maven
JavaWeb 手写Tomcat底层机制
JavaWeb——手写Tomcat底层 BIO线程模型 + 反射机制。
35 0
|
Java 应用服务中间件 容器
tomcat是如何打破双亲委派机制的?
tomcat是如何打破双亲委派机制的?
1001 0
tomcat是如何打破双亲委派机制的?
|
缓存 前端开发 安全
|
4天前
|
前端开发 Java 应用服务中间件
Springboot对MVC、tomcat扩展配置
Springboot对MVC、tomcat扩展配置
|
4天前
|
XML Java 应用服务中间件
Tomcat_servlet部署、编译、配置、打包
Tomcat_servlet部署、编译、配置、打包
23 0
|
4天前
|
运维 Java 应用服务中间件
Tomcat详解(二)——tomcat安装与配置
Tomcat详解(二)——tomcat安装与配置
22 1
|
4天前
|
IDE Java 应用服务中间件
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
18 1
|
4天前
|
网络协议 Java 应用服务中间件
HTTP协议与Tomcat在IJ中配置
本文是对自己学习JavaWeb学习的笔记的总结,添加了一些自己的东西,然后进行一次复盘,并加深一下学习的理解和印象.其中内容主要包括对http协议的详细介绍,java常见服务器的初步介绍,以及IJ旧版和新版的tomcat服务器的配置图解教程