【Tomcat专题】Tomcat如何打破双亲委派机制?

类加载器

三种JDK内部的类加载器

  1. 启动类加载器(BootStrap ClassLoader)

负责加载JRE\lib下的rt.jar、resources.jar、charsets.jar包中的class

  1. 扩展类加载器(Extension ClassLoader)

负责加载jre\lib\ext\目录下面的jar包。

  1. 应用类加载器(Application ClassLoader)

应用类加载器就是负责加载应用路径(classpath)上的类了。

具体可参考下图:
在这里插入图片描述

双亲委派机制

双亲委派机制指的是,当收到一个类加载请求后,ClassLoader首先不会自己直接去加载这个类,而是委派给父类去加载,只有当父类加载器在其路径下没有找到所需要加载的类之后,子类加载器才会自己尝试去加载。

双亲委派的好处

采用双亲委派机制可以避免类的重复加载,以及一些需要保护的类,不会被篡改,比如我们要加载rt.jar包中的java.lang.Object类,不论是哪个类加载器,最终一定要交给启动类加载器(BootstrapClassLoader)来加载,而启动类加载器就负责加载像rt.jar这样的包中的类,从而也保证了最终系统中只有一个Object类。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 先看看有没有已经加载过了
        Class<?> c = findLoadedClass(name);
        // 如果没有加载过
        if (c == null) {
            long t0 = System.nanoTime();
            try {
            	// 如果parent为空,则表示已经交给了Bootstrap类加载器了
                if (parent != null) {
                	// 通过递归的方式,让父类加载器去加载
                    c = parent.loadClass(name, false);
                } else {
                	// 通过bootstrap类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            // 如果父类加载器都没有加载到,就使用findClass方法自己去加载
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

// 通过重写findClass方法实现自定义加载方式
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Tomcat的类加载器

可以看出JVM的双亲委派加载机制关键实现就在loadClass和findClass这个方法中,所以如果要打破双亲委派机制关键就要重写loadClass和findClass这两个方法。

我们先来看看loadClass方法

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(n
        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
        // 先在本地缓存中查找是否已经加载过了
        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
        // 先在本地缓存中查找是否已经加载过了
        clazz = JreCompat.isGraalAvailable() ? null : 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 system class loader, to preve
        //       the webapp from overriding Java SE classes. This implements
        //       SRV.10.7.2
        // 在加载webapp目录之前先尝试使用系统类加载器加载(也就是BootstrapClassLoader或者ExtClassLoader)
        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(re
                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.restricted
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

		// 如果执行到这还没有找到,说明没有被加载过,且也不是JDK中的类
		// 如果delegate为true,则tomcat还是会使用双亲委派加载方式
        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
            }
        }
        // delegate默认为false,所以会使用findClass方法加载
        // (2) Search local repositories
        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
        // 最后都没有加载,则再委托给父加载器加载
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + par
            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);
}

findClass方法则会优先在Web目录中查找要加载的类

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 {
            	// 先在/WEB-INF/classes目录下查找要加载的类
                clazz = findClassInternal(name);
            }
        } catch(AccessControlException ace) {
            log.warn(sm.getString("webappClassLoader.securityException", name,
                    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 {
            	// 如果找不到则交给父类加载器去查找
                clazz = super.findClass(name);
            } catch(AccessControlException ace) {
                log.warn(sm.getString("webappClassLoader.securityException", name,
                        ace.getMessage()), ace);
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
                if (log.isTraceEnabled())
                    log.trace("      -->RuntimeException Rethrown", e);
                throw e;
            }
        }
        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;
}

loadClass总体加载步骤:

  1. 先在cache中查找是否已经加载过,如果有说明Tomcat的类加载器已经加载过了。
  2. 如果Tomcat没有加载过,则先让系统类加载器加载,也就是BootstrapClassLoader或者ExtClassLoader(优先让系统加载器加载能够保证JDK的核心类不会被覆盖加载)。
  3. 如果还是没有,则说明不是JDK中的核心类,那么就通过delegate属性来决定是继续按照双亲委派方式加载,还是按照自定义的方式加载,也就是调用本地的findClass方法,去/WEB-INF/classes目录下查找要加载的类。
  4. 如果这些都没有找到,则回到双亲委派方式交给父类加载器去查找。
  5. 最后都没有找到,则抛出ClassNot在这里插入代码片Found异常。

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值